Home

Awesome

<p><img src="https://raw.githubusercontent.com/TootSDK/TootSDK/main/media/logo.svg" width="100" /></p>

TootSDK

<p><strong>Cross-platform Swift library for the Mastodon API</strong></p> <p> <a href="https://developer.apple.com/swift/"><img alt="Swift 5.7" src="https://img.shields.io/badge/swift-5.7-orange.svg?style=flat"></a> <a href="https://developer.apple.com/swift/"><img alt="Platforms" src="https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-blueviolet"></a> <a href="https://github.com/TootSDK/TootSDK/blob/main/LICENSE.md"><img alt="BSD 3-clause" src="https://img.shields.io/badge/License-BSD_3--Clause-blue.svg"></a> <a href="https://github.com/TootSDK/TootSDK/actions"><img alt="Build Status" src="https://github.com/TootSDK/TootSDK/actions/workflows/build.yml/badge.svg"></a> </p>

TootSDK is a framework for Mastodon and the Fediverse, for iOS. It provides a toolkit for authorizing a user with an instance, and interacting with their posts.

TootSDK is a community developed SDK for Mastodon and the Fediverse. It is designed to work with all major servers (Mastodon, Pleroma, PixelFed etc).

You can use TootSDK to build a client for Apple operating systems, or Linux with Vapor.

overview of how TootSDK integrates with fedi platforms

Why make TootSDK?

When app developers build apps for Mastodon and the Fediverse, every developer ends up having to solve the same set of problems when it comes to the API and data model.

Konstantin and Dave decided to share this effort.

TootSDK is a shared Swift Package that any client app can be built on.

Key Principles ⚙️

Please don't hesitate to open an issue or create a PR for features you need 🙏

Quick start 🏁

It's easy to get started with TootSDK.

  let instanceURL = URL(string: "social.yourinstance.com")
  let client = try await TootClient(connect: instanceURL, accessToken: "USERACCESSTOKEN")

Signing in (for macOS and iOS):

<details> <summary>Network Sandbox Capability/Entitlement (macOS)</summary>

When using TootSDK within a macOS target you will need to enable the com.apple.security.network.client entitlement in your entitlements file or within the Signing & Capabilities tab in Xcode.

<key>com.apple.security.network.client</key>
<true/>

Xcode target view showing the Signing & Capabilities tab with and arrow pointing to a checked Outgoing Connections (Client) option

</details>
let client = try await TootClient(connect: url)
let client = try await TootClient(connect: url)

guard let accessToken = try await client.presentSignIn(callbackURI: callbackURI) else {
    // handle failed sign in
    return
}

That's it 🎉!

We recommend keeping the accessToken somewhere secure, for example the Keychain.

Signing in (all platforms):

let client = try await TootClient(connect: instanceURL)
let authURL = client.createAuthorizeURL(callbackURI: "myapp://someurl")
let accessToken = client.collectToken(returnUrl: url, callbackURI: callbackURI)

We recommend keeping the accessToken somewhere secure, for example the Keychain.

Usage and key concepts

Once you have your client connected, you're going to want to use it. Our example apps and reference docs will help you get into the nitty gritty of it all, but some key concepts are highlighted here.

<details> <summary>Accessing a user's timeline</summary>

There are several different types of timeline in TootSDK that you can access, for example their home timeline, the local timeline of their instance, or the federated timeline. These are all enumerated in the Timeline enum.

You can retrieve the latest posts (up to 40 on Mastodon) with a call like so:

let items = try await client.getTimeline(.home)
let posts = items.result

TootSDK returns Posts, Accounts, Lists and DomainBblocks as PagedResult. In our code, items is a PagedResult struct. It contains a property called result which will be the type of data request (in this case an array of Post).

</details> <details> <summary>Paging requests</summary>

Some requests in TootSDK allow pagination in order to request more information. TootSDK can request a specific page using the PagedInfo struct and handles paginaged server responses using the PagedResult struct.

PagedInfo has the following properties:

So for example, if we want all posts from the user's home timeline that are newer than post ID 100, we could write:

let items = try await client.getTimeline(.home, PagedInfo(minId: 100))
let posts = items.result

Paged requests also deliver a PagedInfo struct as a property of the PagedResult returned, which means you can use that for subsequent requests of the same type.


var pagedInfo: PagedInfo?
var posts: [Post] = []

func retrievePosts() async {
    let items = try await client.getTimeline(.home, pagedInfo)
    posts.append(contentsOf: items.result)
    self.pagedInfo = items.pagedInfo
}

TootSDK implements several facilities to make it easier to iterate over multiple pages using the hasPrevious, hasNext, previousPage and nextPage properties of PagedResult:

var pagedInfo: PagedInfo? = nil
var hasMore = true
let query = TootNotificationParams(types: [.mention])

while hasMore {
  let page = try await client.getNotifications(params: query, pagedInfo)
  for notification in page.result {
    print(notification.id)
  }
  hasMore = page.hasPrevious
  pagedInfo = page.previousPage
}

⚠️ Different fediverse servers handle pagination differently and so there is no guarantee that hasPrevious or hasNext can correctly interpret the server response in all cases.

You can learn more about how pagination works for Fediverse servers using a Mastodon compatible API here.

</details> <details> <summary>Streaming timelines</summary>

In TootSDK it is possible to subscribe to some types of content with AsyncSequences, a concept we've wrapped up in our TootStream object.

for posts in try await client.data.stream(.home) {
    print(posts)
}

Underneath the hood, this uses our Paging mechanism. This means that when you ask the client to refresh that stream, it will deliver you new results, from after the ones you requested.

client.data.refresh(.home)

You can also pass an initial PagedInfo value to the stream call. For example, to start steaming all posts from the user's home timeline that are newer than post ID 100:

for posts in try await client.data.stream(.home, PagedInfo(minId: 100) {

Some timelines require associated query parameters to configure. Luckily these are associated values that their timeline enumeration require when creating - so you can't miss them!


for posts in try await client.data.stream(HashtagTimelineQuery(tag: "iOSDev") {
    print(posts)
}
</details> <details> <summary>Creating an account</summary>
let instance = try await client.getInstance()
if instance.registrations == false {
  // instance not open for registration
  return
}
// ...
let params = RegisterAccountParams(
      username: name, email: email, password: password, agreement: true, locale: "en")
let token = try await client.registerAccount(params: params)
</details>

Further Documentation 📖

Contributing

Code of Conduct and Contributing rules 🧑‍⚖️

License 📃

TootSDK is licensed with the BSD-3-Clause license, more information here: LICENSE.MD

This is a permissive license which allows for any type of use, provided the copyright notice is included.

Acknowledgements 🙏

Built with TootSDK