TwitterAPIKit

用于 Twitter API v1 和 v2 的 Swift 库。

Swift Standard v2 codecov

请查看此 issue 以了解 API 实现的进度。

Issue 或评论用日语也可以。


动机

不幸的是,目前我找不到任何活跃的 Swift Twitter API 库。

所以,我决定创建一个。

策略

API 结构

您可以根据您的应用程序限制可用 API 的范围。如果您的应用程序仅支持 v1,或者您想限制对 API 的访问,这将非常有用。目前,根据 Twitter 的应用程序权限进行范围界定尚未实现。

// The most common usage.

// For OAuth 1.0a
let client = TwitterAPIClient(.oauth10a(.init(
            consumerKey: "",
            consumerSecret: "",
            oauthToken: "",
            oauthTokenSecret: ""
        )))
// For OAuth 2.0 client
let client = TwitterAPIClient(.oauth20(.init(
            clientID: "",
            scope: [],
            tokenType: "",
            expiresIn: 0,
            accessToken: "",
            refreshToken: ""
        )))

client.v1.someV1API()
client.v2.someV2API()

// V1 only client
let v1Client = client.v1
v1Client.someV1API()

// V2 only client
let v2Client = client.v2
v2Client.someV2API()

// DM only client
let dmClient = client.v1.directMessage
dmClient.someDM_APIs()

// Each API can be accessed flatly or by individual resource.

// Flat.
let client.v1.allV1_APIs()

// Individual resources.
let client.v1.tweet.someTweetAPIs()
let client.v1.directMessage.someDM_APIs()

如何进行身份验证?

请参阅 “HowDoIAuthenticate.md”

以下示例项目包含身份验证示例。

https://github.com/mironal/TwitterAPIKit-iOS-sample

如何解码响应

请参阅 “HowToDecodeResponse.md”

Linux 支持(实验性)

TwitterAPIKit 可以在 Linux 上使用,但由于无法运行测试,因此无法合并到主分支。

如果您想在 Linux 上使用它,请使用此分支

示例

项目

此示例项目包含如何使用 OAuth 1.0a User Access Tokens (3-legged OAuth flow)OAuth 2.0 Authorization Code Flow with PKCE 进行身份验证的示例。

基本

    let consumerKey = ""
    let consumerSecret = ""
    let oauthToken = ""
    let oauthTokenSecret = ""

    let client = TwitterAPIClient(
        consumerKey: consumerKey,
        consumerSecret: consumerSecret,
        oauthToken: oauthToken,
        oauthTokenSecret: oauthTokenSecret
    )

    client.v1.getShowStatus(.init(id: "status id"))
         // Already serialized using "JSONSerialization.jsonObject(with:, options:)".
        .responseObject() { response in }
        .responseObject(queue: .global(qos: .default)) { response in  }

        // Already decoded using JSONDecoder.
        .responseDecodable(type: Entity.self, queue: .global(qos: .default)) { response in }
        .responseDecodable(type: Entity.self) { response in }

        // Unprocessed data
        .responseData() { response in /* Run in .main queue */ }
        .responseData((queue: .global(qos: .default)) { response in /* Run in .global(qos: .default) queue  */ }

        // !! A `prettyString` is provided for debugging purposes. !!
        print(response.prettyString)

        result.map((Success) -> NewSuccess)
        result.tryMap((Success) throws -> NewSuccess)
        result.mapError((TwitterAPIKitError) -> TwitterAPIKitError>)
        result.success // Success?
        result.error // TwitterAPIKitError?
        response.rateLimit

        // Use result
        do {
            let success = try response.result.get()
            print(success)
        } catch let error {
            print(error)
        }
    }

刷新 OAuth 2.0 Token

let refresh = try await client.refreshOAuth20Token(type: .confidentialClient(clientID: "", clientSecret: ""), forceRefresh: true)
// let refresh = try await client.refreshOAuth20Token(type: .publicClient, forceRefresh: true)

// The authentication information in the Client is also updated, so there is no need to recreate a new instance of the Client.

if refresh.refreshed {
    storeToken(refresh.token)
}

// Or

client.refreshOAuth20Token(type: .publicClient, forceRefresh: true) { result in
    do {
        let refresh = try result.get()
        if refresh.refreshed {
            storeToken(refresh.token)
        }
    } catch {

    }
}

// Notification

NotificationCenter.default.addObserver(
    self,
    selector: #selector(didRefreshOAuth20Token(_:)),
    name: TwitterAPIClient.didRefreshOAuth20Token,
    object: nil
)

@objc func didRefreshOAuth20Token(_ notification: Notification) {
    guard let token = notification.userInfo?[TwitterAPIClient.tokenUserInfoKey] as? TwitterAuthenticationMethod.OAuth20 else {
        fatalError()
    }
    print("didRefreshOAuth20Token", didRefreshOAuth20Token, token)
    store(token)
}

自定义请求类

可以继承每个请求的类来创建子类。这就是为什么它被声明为 open class 而不是 struct 的原因。

这样做的目的是,当 Twitter API 发生更改导致添加新参数时,您可以自行处理它们,而无需等待库更新。

// example
class CustomListsListRequestV1: GetListsListRequestV1 {

    let custom: String

    override var parameters: [String: Any] {
        var p = super.parameters
        p["custom"] = custom
        return p
    }

    init(custom: String, user: TwitterUserIdentifierV1, reverse: Bool? = .none) {
        self.custom = custom
        super.init(user: user, reverse: reverse)
    }
}

也可以创建封装的自定义请求类。

class CapsuledListsListRequestV1: GetListsListRequestV1 {
    init() {
        super.init(user: .userID("100"), reverse: true)
    }
}

底层 API

此方法旨在用于当库尚不支持 Twitter 的新 API 时。

    class YourCustomRequest: TwitterAPIRequest {
        // write code...
    }


    let consumerKey = ""
    let consumerSecret = ""
    let oauthToken = ""
    let oauthTokenSecret = ""

    let client = TwitterAPIClient(
        consumerKey: consumerKey,
        consumerSecret: consumerSecret,
        oauthToken: oauthToken,
        oauthTokenSecret: oauthTokenSecret
    )

    let request = YourCustomRequest()
    client.session.send(request)
}

Swift 并发(实验性)

Task {
    let result = try await client.v1.timeline.getHomeTimeline(.init()).responseData // or responseObject or response responseDecodable(type: Hoge.self)

    print(result.prettyString)
}

Stream API

TODO