Skip to content

Commit b4ff318

Browse files
committed
add getHomeTimeline
1 parent 48e0b39 commit b4ff318

11 files changed

Lines changed: 327 additions & 3 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
/// https://developer.twitter.com/en/docs/twitter-api/v1/tweets/timelines/api-reference/get-statuses-home_timeline
4+
public struct GetHomeTimelineParameter: TwitterAPIParameter {
5+
6+
public let count: Int
7+
8+
let method: HTTPMethod = .get
9+
let path: String = "/1.1/statuses/home_timeline.json"
10+
var parameters: [String: Any]? {
11+
return [
12+
"count": count
13+
]
14+
}
15+
16+
public init(
17+
count: Int
18+
) {
19+
self.count = count
20+
}
21+
}
22+
23+
extension TwitterAPIKit: TwitterAPIv1 {
24+
public func getHomeTimeline(
25+
_ parameter: GetHomeTimelineParameter,
26+
completionHandler: @escaping (Result<(Data, HTTPURLResponse), TwitterAPIKitError>) -> Void
27+
) -> URLSessionTask {
28+
return session.request(parameter, completionHandler: completionHandler)
29+
}
30+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
public protocol TwitterAPIv1 {
4+
5+
func getHomeTimeline(
6+
_ parameter: GetHomeTimelineParameter,
7+
completionHandler: @escaping (Result<(Data, HTTPURLResponse), TwitterAPIKitError>) -> Void
8+
) -> URLSessionTask
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
public protocol TwitterAPIv2 {
4+
5+
}
6+
7+
extension TwitterAPIKit: TwitterAPIv2 {
8+
9+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import CommonCrypto
2+
import Foundation
3+
4+
extension Data {
5+
func hmac(key: Data) -> Data {
6+
7+
// Thanks: https://github.com/jernejstrasner/SwiftCrypto
8+
9+
let digestLen = Int(CC_SHA1_DIGEST_LENGTH)
10+
return withUnsafeBytes { bytes -> Data in
11+
let result = UnsafeMutablePointer<UInt8>.allocate(capacity: digestLen)
12+
defer {
13+
result.deallocate()
14+
}
15+
16+
key.withUnsafeBytes { body in
17+
CCHmac(
18+
CCHmacAlgorithm(kCCHmacAlgSHA1),
19+
body.baseAddress,
20+
key.count, bytes.baseAddress,
21+
count,
22+
result
23+
)
24+
}
25+
26+
return Data(bytes: result, count: digestLen)
27+
}
28+
}
29+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
3+
extension Dictionary {
4+
5+
var urlEncodedQueryString: String {
6+
var parts = [String]()
7+
8+
for (key, value) in self {
9+
let keyString = "\(key)".urlEncodedString
10+
let valueString = "\(value)".urlEncodedString
11+
let query: String = "\(keyString)=\(valueString)"
12+
parts.append(query)
13+
}
14+
15+
return parts.joined(separator: "&")
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Foundation
2+
3+
extension String {
4+
var urlEncodedString: String {
5+
var allowedCharacterSet: CharacterSet = .urlQueryAllowed
6+
allowedCharacterSet.remove(charactersIn: "\n:#/?@!$&'()*+,;=")
7+
allowedCharacterSet.insert(charactersIn: "[]")
8+
return self.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)!
9+
}
10+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import CommonCrypto
2+
import Foundation
3+
4+
private let OAUTH_VERSION = "1.0"
5+
private let OAUTH_SIGNATURE_METHOD = "HMAC-SHA1"
6+
7+
func authorizationHeader(
8+
for method: HTTPMethod,
9+
url: URL,
10+
parameters: [String: Any],
11+
isMediaUpload: Bool,
12+
consumerKey: String,
13+
consumerSecret: String,
14+
oauthToken: String?,
15+
oauthTokenSecret: String?
16+
) -> String {
17+
var authorizationParameters = [String: Any]()
18+
authorizationParameters["oauth_version"] = OAUTH_VERSION
19+
authorizationParameters["oauth_signature_method"] = OAUTH_SIGNATURE_METHOD
20+
authorizationParameters["oauth_consumer_key"] = consumerKey
21+
authorizationParameters["oauth_timestamp"] = String(Int(Date().timeIntervalSince1970))
22+
authorizationParameters["oauth_nonce"] = UUID().uuidString
23+
24+
if let oauthToken = oauthToken {
25+
authorizationParameters["oauth_token"] = oauthToken
26+
}
27+
28+
for (key, value) in parameters where key.hasPrefix("oauth_") {
29+
authorizationParameters.updateValue(value, forKey: key)
30+
}
31+
32+
let combinedParameters = authorizationParameters.merging(parameters) { $1 }
33+
34+
let finalParameters = isMediaUpload ? authorizationParameters : combinedParameters
35+
36+
authorizationParameters["oauth_signature"] = oauthSignature(
37+
for: method, url: url, parameters: finalParameters, consumerSecret: consumerSecret,
38+
oauthTokenSecret: oauthTokenSecret)
39+
40+
let authorizationParameterComponents = authorizationParameters.urlEncodedQueryString.components(
41+
separatedBy: "&"
42+
).sorted()
43+
44+
var headerComponents = [String]()
45+
for component in authorizationParameterComponents {
46+
let subcomponent = component.components(separatedBy: "=")
47+
if subcomponent.count == 2 {
48+
headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"")
49+
}
50+
}
51+
52+
return "OAuth " + headerComponents.joined(separator: ", ")
53+
}
54+
55+
private func oauthSignature(
56+
for method: HTTPMethod,
57+
url: URL,
58+
parameters: [String: Any],
59+
consumerSecret: String,
60+
oauthTokenSecret: String?
61+
) -> String {
62+
let tokenSecret = oauthTokenSecret?.urlEncodedString ?? ""
63+
let encodedConsumerSecret = consumerSecret.urlEncodedString
64+
let signingKey = "\(encodedConsumerSecret)&\(tokenSecret)"
65+
let parameterComponents = parameters.urlEncodedQueryString.components(separatedBy: "&").sorted()
66+
let parameterString = parameterComponents.joined(separator: "&")
67+
let encodedParameterString = parameterString.urlEncodedString
68+
let encodedURL = url.absoluteString.urlEncodedString
69+
let signatureBaseString = "\(method.rawValue)&\(encodedURL)&\(encodedParameterString)"
70+
71+
let key = signingKey.data(using: .utf8)!
72+
let msg = signatureBaseString.data(using: .utf8)!
73+
let sha1 = msg.hmac(key: key)
74+
return sha1.base64EncodedString(options: [])
75+
}
Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
1-
public struct TwitterAPIKit {
2-
public private(set) var text = "Hello, World!"
1+
import Foundation
32

4-
public init() {
3+
public enum TwitterAPIAuth {
4+
case oauth(
5+
consumerKey: String,
6+
consumerSecret: String,
7+
oauthToken: String?,
8+
oauthTokenSecret: String?
9+
)
10+
}
11+
12+
open class TwitterAPIKit {
13+
14+
public var v1: TwitterAPIv1 { return self }
15+
public var v2: TwitterAPIv2 { return self }
16+
17+
public let session: TwitterAPISession
18+
public var apiAuth: TwitterAPIAuth {
19+
return session.auth
20+
}
21+
22+
public init(
23+
consumerKey: String, consumerSecret: String, oauthToken: String, oauthTokenSecret: String
24+
) {
25+
session = TwitterAPISession(
26+
auth: .oauth(
27+
consumerKey: consumerKey, consumerSecret: consumerSecret, oauthToken: oauthToken,
28+
oauthTokenSecret: oauthTokenSecret))
529
}
630
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
public enum HTTPMethod: String {
4+
case get = "GET"
5+
case post = "POST"
6+
case delete = "DELETE"
7+
8+
public var prefersQueryParameters: Bool {
9+
switch self {
10+
case .get, .delete:
11+
return true
12+
default:
13+
return false
14+
}
15+
}
16+
}
17+
18+
protocol TwitterAPIParameter {
19+
var baseURL: URL { get }
20+
var method: HTTPMethod { get }
21+
22+
var path: String { get }
23+
var parameters: [String: Any]? { get }
24+
}
25+
26+
extension TwitterAPIParameter {
27+
public var baseURL: URL {
28+
return URL(string: "https://api.twitter.com")!
29+
}
30+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Foundation
2+
3+
public protocol TwitterAPIRequest {
4+
5+
func response(
6+
completionHandler: @escaping (Result<Data, TwitterAPIKitError>, HTTPURLResponse) -> Void)
7+
}

0 commit comments

Comments
 (0)