Skip to content
/ Twift Public

🐦 An async Swift library for the Twitter v2 API 🚧 WIP

License

Notifications You must be signed in to change notification settings

daneden/Twift

Repository files navigation

Twift

Twitter API v2 badge Documentation Coverage Run Tests Build Demo App

Twift is an asynchronous Swift library for the Twitter v2 API.

  • No external dependencies
  • Fully async
  • Full Swift type definitions/wrappers around Twitter's API objects

Quick Start Guide

New Twift instances must be initiated with either OAuth 2.0 user authentication or an App-Only Bearer Token:

// OAuth 2.0 User Authentication
let oauthUser: OAuth2User = OAUTH2_USER
let userAuthenticatedClient = Twift(oauth2User: oauthUser, onTokenRefresh: saveUserCredentials)

// App-Only Bearer Token
let appOnlyClient = Twift(appOnlyBearerToken: BEARER_TOKEN)

You can authenticate users with Twift.Authentication().authenticateUser():

var client: Twift?

do {
  let oauthUser = try await Twift.Authentication().authenticateUser(
    clientId: TWITTER_CLIENT_ID,
    redirectUri: URL(string: TWITTER_CALLBACK_URL)!,
    scope: Set(OAuth2Scope.allCases)
  )
  
  client = Twift(oauth2User: oauthUser, onTokenRefresh: saveUserCredentials)
  
  // It's recommended that you store your user auth tokens via Keychain or another secure storage method.
  // OAuth2User can be encoded to a data object for storage and later retrieval.
  saveUserCredentials(oauthUser) // Saves the data to Keychain, for example
} catch {
  print(error.localizedDescription)
}

Once initiated, you can begin calling methods appropriate for the authentication type:

do {
  // User objects always return id, name, and username properties,
  // but additional properties can be requested by passing a `fields` parameter
  let authenticatedUser = try await userAuthenticatedClient.getMe(fields: [\.profilePhotoUrl, \.description])
  
  // Non-standard properties are optional and require unwrapping
  if let description = authenticatedUser.description {
    print(description)
  }
} catch {
  print(error.localizedDescription)
}

Posting Tweets supports text and polls. Media methods are OAuth 1.0a only and the API may change significantly when Twitter's v2 API adds new media endpoints.

do {
  let textOnlyTweet = MutableTweet(text: "This is a test Tweet")
  try await twitterClient.postTweet(textOnlyTweet)
  
  let poll = try MutablePoll(options: ["Soft g", "Hard g"])
  let tweetWithPoll = MutableTweet(text: "How do you pronounce 'gif'?", poll: poll)
  try await twitterClient.postTweet(tweetWithPoll)
} catch {
  print(error)
}

Requirements

To be completed

Documentation

You can find the full documentation in this repo's Wiki (auto-generated by SwiftDoc).

Quick Tips

Disambiguating List

If you use Twift with SwiftUI, sooner or later you might run into the problem of needing to disambiguate Twift.List from SwiftUI.List. It is recommended that you assign a typealias in a file that doesn't import SwiftUI to disambiguate between the types:

import struct Twift.List

typealias TwitterList = Twift.List

Typical Method Return Types

Twift's methods generally return TwitterAPI[...] objects containing up to four properties:

  • data, which contains the main object(s) you requested (e.g. for the getUser endpoint, this contains a User)
  • includes, which includes any expansions you request (e.g. for the getUser endpoint, you can optionally request an expansion on pinnedTweetId; this would result in the includes property containing a Tweet)
  • meta, which includes information about pagination (such as next/previous page tokens and result counts)
  • errors, which includes an array of non-failing errors

All of the methods are throwing, and will throw either a TwiftError, indicating a problem related to the Twift library (such as incorrect parameters passed to a method) or a TwitterAPIError, indicating a problem sent from Twitter's API as a response to the request.

Fields and Expansions

Many of Twift's methods accept two optional parameters: fields and expansions. These parameters allow you to request additional fields (properties) on requested objects, as well as expansions on associated objects. For example:

// Returns the currently-authenticated user
let response = try? await userAuthenticatedClient.getMe(
  // Asks for additional fields: the profile image URL, and the user's description/bio
  fields: [\.profileImageUrl, \.description],
  
  // Asks for expansions on associated fields; in this case, the pinned Tweet ID.
  // This will result in a Tweet on the returned `TwitterAPIDataAndIncludes.includes`
  expansions: [
    // Asks for additional fields on the Tweet: the Tweet's timestamp, and public metrics (likes, retweets, and replies)
    .pinnedTweetId([
      \.createdAt,
      \.publicMetrics
    ])
  ]
)

// The user object
let me = response?.data

// The user's pinned Tweet
let tweet = response?.includes?.tweets.first