Skip to content

Commit

Permalink
Merge pull request #94 from will-lumley/feature/add-support-for-og
Browse files Browse the repository at this point in the history
[FEAT] Add Support for OpenGraph Type
  • Loading branch information
will-lumley authored Sep 28, 2024
2 parents 70b327a + 794ecb7 commit a81fef8
Show file tree
Hide file tree
Showing 21 changed files with 318 additions and 82 deletions.
19 changes: 12 additions & 7 deletions .github/workflows/BuildTests-iOS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ jobs:
build:
name: Build and Test (iOS)
runs-on: macos-14

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Set Xcode version
run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
- name: Install xcpretty
run: gem install xcpretty

- name: Setup Swift
uses: SwiftyLab/setup-swift@latest
uses: SwiftyLab/setup-swift@v1
with:
swift-version: '6.0'
swift-version: '6.0.0'
cache-snapshot: 'false'

- name: Install xcpretty
run: gem install xcpretty
- name: Check Xcode Version
run: xcodebuild -version

- name: Install Dependencies
run: xcodebuild -resolvePackageDependencies -verbose

- name: Test
run: xcodebuild test -scheme FaviconFinder -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' -skipPackagePluginValidation
run: xcodebuild test -scheme FaviconFinder -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' -skipPackagePluginValidation
2 changes: 1 addition & 1 deletion LinuxFaviconFinderExample/Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.5
// swift-tools-version: 6.0.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:6.0
// swift-tools-version:6.0.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ To install it, simply add the dependency to your Package.Swift file:
```swift
...
dependencies: [
.package(url: "https://github.com/will-lumley/FaviconFinder.git", from: "5.1.0"),
.package(url: "https://github.com/will-lumley/FaviconFinder.git", from: "5.1.1"),
],
targets: [
.target( name: "YourTarget", dependencies: ["FaviconFinder"]),
Expand Down
24 changes: 19 additions & 5 deletions Sources/FaviconFinder/FaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ public final class FaviconFinder: @unchecked Sendable {

// MARK: - Lifecycle

public init(url: URL, configuration: FaviconFinder.Configuration = .defaultConfiguration) {
public init(
url: URL,
configuration: FaviconFinder.Configuration = .defaultConfiguration
) {
self.url = url
self.configuration = configuration
}
Expand Down Expand Up @@ -67,6 +70,7 @@ public extension FaviconFinder {
// Iterate through each source, trying to find the favicon
// in each source until we find it.
for source in sources {
print("Using source: [\(source)]")
do {
let finder = source.finder(url: url, configuration: config)
let faviconURLs = try await finder.find()
Expand All @@ -76,14 +80,14 @@ public extension FaviconFinder {
throw CancellationError()
} catch let error as NSError {
// Check if the error is a URL cancellation error
if error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled {
// Map this to Swift's `CancellationError`
// Map this to Swift's `CancellationError`s
if error.isCancelled {
throw CancellationError()
} else {
// print("Failed to find Favicon [\(error)]. Trying next source type.")
print("Failed to find Favicon [\(error)]. Trying next source type.")
}
} catch {
// print("Failed to find Favicon [\(error)]. Trying next source type.")
print("Failed to find Favicon [\(error)]. Trying next source type.")
}
}

Expand All @@ -103,3 +107,13 @@ public extension FaviconFinder {
}

}

// MARK: - NSError

private extension NSError {

var isCancelled: Bool {
self.code == NSURLErrorCancelled
}

}
103 changes: 95 additions & 8 deletions Sources/FaviconFinder/Finders/HTMLFaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class HTMLFaviconFinder: FaviconFinderProtocol {

// MARK: - Types

struct Reference {
struct HtmlReference {
let rel: String
let href: String
let sizeTag: String?
Expand All @@ -21,6 +21,15 @@ class HTMLFaviconFinder: FaviconFinderProtocol {
let format: FaviconFormatType
}

struct OpenGraphicReference {
let type: String
let content: String
let size: FaviconSize?

let baseURL: URL
let format: FaviconFormatType
}

// MARK: - Properties

var url: URL
Expand Down Expand Up @@ -68,8 +77,25 @@ class HTMLFaviconFinder: FaviconFinderProtocol {
// Get all the "link" favicon tags from our head
let links = try self.links(from: head)

let faviconURLs = links.map {
FaviconURL(source: $0.baseURL, format: $0.format, sourceType: .html, sizeTag: $0.sizeTag)
// Create FaviconURLs from our links
var faviconURLs = links.map {
FaviconURL(
source: $0.baseURL,
format: $0.format,
sourceType: .html,
htmlSizeTag: $0.sizeTag
)
}

// Create FaviconURLs from our metas
let metas = try self.metas(from: head)
faviconURLs += metas.map {
FaviconURL(
source: $0.baseURL,
format: $0.format,
sourceType: .html,
size: $0.size
)
}

if faviconURLs.isEmpty {
Expand All @@ -88,20 +114,21 @@ private extension HTMLFaviconFinder {
/// Will extrapolate all the "link" elements from the provided HTML header element, and
/// return the ones that correlate to favicon imgaes
///
/// - Parameter htmlHead: Our HTML header elelment
/// - Returns: An array of "link" elements, formatted in our internal `Reference` struct
/// - parameter htmlHead: Our HTML header elelment
/// - returns: An array of "link" elements, formatted in our internal `Reference` struct
///
func links(from htmlHead: Element) throws -> [Reference] {
func links(from htmlHead: Element) throws -> [HtmlReference] {
// Where we're going to store our HTML favicons
var links = [Reference]()
var links = [HtmlReference]()

// Iterate over every 'link' tag that's in the head document, and collect them
for link in try htmlHead.select("link") {
let rel = try link.attr("rel")
let href = try link.attr("href")
let sizeTag = try link.attr("sizes")

// If this link's "rel" is something other than an accepted image format type, dismiss it
// If this link's "rel" is something other than an accepted image
// format type, dismiss it
guard FaviconFormatType(rawValue: rel) != nil else {
continue
}
Expand Down Expand Up @@ -131,4 +158,64 @@ private extension HTMLFaviconFinder {
return links
}

/// Will extrapolate all the "meta" elements from the provided HTML header element, and
/// return the ones that correlate to favicon imgaes
///
/// - parameter htmlHead: Our HTML header elelment
/// - returns: An array of "link" elements, formatted in our internal `Reference` struct
///
func metas(from htmlHead: Element) throws -> [OpenGraphicReference] {
// Where we're going to store our HTML favicons
var metas = [OpenGraphicReference]()

// Iterate over every 'link' tag that's in the head document, and collect them
for meta in try htmlHead.select("meta") {
var property = try meta.attr("property")
let content = try meta.attr("content")

// If this link's "property" is something other than an accepted image
// format type, dismiss it
if FaviconFormatType(rawValue: property) == nil {

// Okay so "property" gave us nothing, let's try name
property = try meta.attr("name")
if FaviconFormatType(rawValue: property) == nil {
// Still nothing, onto the next one
continue
}
}

// Get the base URL from the href
guard let baseURL = content.baseUrl(from: htmlHead, from: self.url) else {
continue
}

// Get the format type in our own internal type
guard let format = FaviconFormatType(rawValue: property) else {
continue
}

// Pull out the size if we can
var size: FaviconSize?
if
let width = content.valueOfQueryParam("width"),
let height = content.valueOfQueryParam("height") {
size = .init(widthStr: width, heightStr: height)
}

// Add the potential favicon type to our links array
metas.append(
.init(
type: property,
content: content,
size: size,
baseURL: baseURL,
format: format
)
)
}

return metas
}

}
16 changes: 14 additions & 2 deletions Sources/FaviconFinder/Finders/ICOFaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ class ICOFaviconFinder: FaviconFinderProtocol {

// We found valid image data, woohoo!
if (try? FaviconImage(data: fullFaviconUrlData)) != nil {
return [FaviconURL(source: faviconUrl, format: .ico, sourceType: .ico, sizeTag: nil)]
return [
FaviconURL(
source: faviconUrl,
format: .ico,
sourceType: .ico
)
]
}

// We couldn't find any image, so let's try the root domain (just in case it's hiding there)
Expand All @@ -62,7 +68,13 @@ class ICOFaviconFinder: FaviconFinderProtocol {

if (try? FaviconImage(data: baseFaviconUrlData)) != nil {
// We found valid image data, woohoo!
return [FaviconURL(source: rootURL, format: .ico, sourceType: .ico, sizeTag: nil)]
return [
FaviconURL(
source: rootURL,
format: .ico,
sourceType: .ico
)
]
} else {
// Well we couldn't find any valid image data at the provided URL, nor the root domain, game over.
throw FaviconError.failedToFindFavicon
Expand Down
6 changes: 3 additions & 3 deletions Sources/FaviconFinder/Finders/MockFaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@ class MockFaviconFinder: FaviconFinderProtocol {
source: URL(string: "https://google.com")!,
format: .appleTouchIcon,
sourceType: .html,
sizeTag: "100x140"
htmlSizeTag: "100x140"
),
.init(
source: URL(string: "https://apple.com")!,
format: .appleTouchIcon,
sourceType: .html,
sizeTag: "100x90"
htmlSizeTag: "100x90"
),
.init(
source: URL(string: "https://facebook.com")!,
format: .appleTouchIcon,
sourceType: .html,
sizeTag: "100x90"
htmlSizeTag: "100x90"
)
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class WebApplicationManifestFaviconFinder: FaviconFinderProtocol {
source: source,
format: format,
sourceType: .webApplicationManifestFile,
sizeTag: sizeTag
htmlSizeTag: sizeTag
)
}

Expand Down
25 changes: 25 additions & 0 deletions Sources/FaviconFinder/Toolbox/Extensions/String+URLQuery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// String+URLQuery.swift
// FaviconFinder
//
// Created by William Lumley on 25/9/2024.
//

import Foundation

extension String {

/// Extracts the value of a query parameter by its name from a URL string.
/// - parameter name: The name of the query parameter.
/// - returns: The value of the query parameter, or nil if not found.
///
func valueOfQueryParam(_ name: String) -> String? {
guard let url = URL(string: self),
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else {
return nil
}
return queryItems.first(where: { $0.name == name })?.value
}

}
4 changes: 4 additions & 0 deletions Sources/FaviconFinder/Types/FaviconFormatType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public enum FaviconFormatType: String, CaseIterable, Equatable, Sendable {
case shortcutIcon = "shortcut icon"
case icon = "icon"

// OpenGraphic Types
case metaThumbnail = "thumbnail"
case metaOpenGraphImage = "og:image"

// Filetype (ico)
case ico = "ico"

Expand Down
43 changes: 43 additions & 0 deletions Sources/FaviconFinder/Types/FaviconSize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Size.swift
// FaviconFinder
//
// Created by William Lumley on 26/9/2024.
//

public struct FaviconSize: Equatable, Sendable {

// MARK: - Properties

public let width: Double
public let height: Double

// MARK: - Lifecycle

init(width: Double, height: Double) {
self.width = width
self.height = height
}

init?(widthStr: String, heightStr: String) {
guard
let width = Double(widthStr),
let height = Double(heightStr) else {
return nil
}

self.width = width
self.height = height
}

}

// MARK: - Public

public extension FaviconSize {

var dimension: Double {
self.width * self.height
}

}
Loading

0 comments on commit a81fef8

Please sign in to comment.