Giter VIP home page Giter VIP logo

notionswift's Introduction

NotionSwift

Unofficial Notion SDK for iOS & macOS.

This is still work in progress version, the module interface might change.

API Documentation

This library is a client for the official Notion API. For more details and documentation please check Notion Developer Portal

Installation

CocoaPods

pod 'NotionSwift', '0.8.0'

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/chojnac/NotionSwift.git", .upToNextMajor(from: "0.8.0"))
]

Usage

Currently, this library supports only the "internal integration" authorization mode. For more information about authorization and instruction how to obtain NOTION_TOKEN please check Notion Offical Documentation.

Important: Integrations are granted access to resources (pages and databases) which users have shared with the integration. Resources that are not shared with the integration are not visible by API endpoints.

Creating a Notion client

let notion = NotionClient(accessKeyProvider: StringAccessKeyProvider(accessKey: "{NOTION_TOKEN}"))

Tweak network configuration

To tweak things like network timeouts you can provide a custom URLSessionConfiguration to NotionClient like below.

let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 15
let notion = NotionClient(accessKeyProvider: StringAccessKeyProvider(accessKey: "{NOTION_TOKEN}"), sessionConfiguration: sessionConfig)

If that's not enough for your needs, you can implement the NetworkClient protocol and provide your implementation to NotionClient.

List all databases

The https://api.notion.com/v1/databases is deprecated. To recommended way to list all databases is to use https://api.notion.com/v1/search endpoint. In theory, search allows filtering results by object type. However, currently, the only filter allowed is object which will filter by type of object (either page or database) To narrow search results, use code snippet belove.

// fetch available databases
notion.search(request: .init(filter: .database)) { result in
    let databases = result.map { objects in
        objects.results.compactMap({ object -> Database? in
            if case .database(let db) = object {
                return db
            }
            return nil
        })
    }
    print(databases)
}

Query a database

In this example we will get all pages in the database. To narrow results use params argument.

let databaseId = Database.Identifier("{DATABASE UUIDv4}")

notion.databaseQuery(databaseId: databaseId) {
    print($0)
}

Retrieve a database

let databaseId = Database.Identifier("{DATABASE UUIDv4}")

notion.database(databaseId: databaseId) {
    print($0)
}

Create a database

let parentPageId = Page.Identifier("e67db074-973a-4ddb-b397-66d3c75f9ec9")

let request = DatabaseCreateRequest(
    parent: .pageId(parentPageId),
    icon: .emoji("🤔"),
    cover: .external(url: "https://images.unsplash.com/photo-1606787366850-de6330128bfc"),
    title: [
        .init(string: "Created at: \(Date())")
    ],
    properties: [
        "Field 10": .richText
    ]
)

notion.databaseCreate(request: request) {
    print($0)
}

Update a database

let id = Database.Identifier("{DATABASE UUIDv4}")

// update cover, icon & add a new field
let request = DatabaseUpdateRequest(
    title: nil,
    icon: .emoji("🤔"),
    cover: .external(url: "https://images.unsplash.com/photo-1606787366850-de6330128bfc"),
    properties: [
        "Field 10": .richText
    ]
)

notion.databaseUpdate(databaseId: id, request: request) {
    print($0)
}

Create a database entry

Notion database entries are pages, whose properties conform to the parent database's schema.

let databaseId = Database.Identifier("{DATABASE UUIDv4}")

let request = PageCreateRequest(
    parent: .database(databaseId),
    properties: [
        "title": .init(
            type: .title([
                .init(string: "Lorem ipsum \(Date())")
            ])
        ),
        "Field 10": .init(
            type: .richText([
                .init(string: "dolor sit amet")
            ])
        )
    ]
)

notion.pageCreate(request: request) {
    print($0)
}

Retrieve a page

Retrieve page properties.

let pageId = Page.Identifier("{PAGE UUIDv4}")

notion.page(pageId: pageId) {
    print($0)
}

Page content (text for example) is represented as an array of blocks. The example below loads properties and page content.

let pageId = Page.Identifier("{PAGE UUIDv4}")

notion.page(pageId: pageId) { [notion] in
    print("---- Properties ----- ")
    print($0)
    switch $0 {
    case .success(let page):
        notion.blockChildren(blockId: page.id.toBlockIdentifier) {
            print("---- Children ----- ")
            print($0)
        }
    default:
        break
    }
}

Note: The API returns only the direct children of the page. If there is content nested in the block (nested lists for example) it requires other calls.

Create a page

let parentPageId = Page.Identifier("{PAGE UUIDv4}")

let request = PageCreateRequest(
    parent: .page(parentPageId),
    properties: [
        "title": .init(
            type: .title([
                .init(string: "Lorem ipsum \(Date())")
            ])
        )
    ]
)

notion.pageCreate(request: request) {
    print($0)
}

Update page properties

let pageId = Page.Identifier("{PAGE UUIDv4}")

// update title property
let request = PageProperiesUpdateRequest(
    properties: [
        .name("title"): .init(
            type: .title([
                .init(string: "Updated at: \(Date())")
            ])
        )
    ]
)

notion.pageUpdateProperties(pageId: pageId, request: request) {
    print($0)
}

Retrieve block children

Note: This endpoint returns only the first level of children, so for example, nested list items won't be returned. In that case, you need to make another request with the block id of the parent block.

let pageId = Block.Identifier("{PAGE UUIDv4}")

notion.blockChildren(blockId: pageId) {
    print($0)
}

Append block children

let pageId = Block.Identifier("{PAGE UUIDv4}")

// append paragraph with styled text to a page.
let blocks: [WriteBlock] = [
    .heading1(["Heading 1"], color: .orange),
    .paragraph([
        "Lorem ipsum dolor sit amet, ",
        .init(string: "consectetur", annotations: .bold),
        " adipiscing elit."
    ]),
    .heading2(["Heading 2"], color: .orangeBackground),
    .columnList(columns: [
        .column([
            .paragraph(["Column 1"])
        ]),
        .column([
            .paragraph(["Column 2"])
        ])
    ]),
    try! .table(
        width: 2,
        headers: [
            ["Header 1"], ["Header 2"]
        ],
        rows: [
            .row(
                header: ["Row 1 header"],
                cells: [
                    ["Cell 1-1"], ["Cell 1-2"]
                ]
            ),
            .row(
                cells: [
                    ["Cell 2-1"], ["Cell 2-2"]
                ]
            )
        ]
    )
]
notion.blockAppend(blockId: pageId, children: blocks) {
    print($0)
}

Update a block

let blockId = Block.Identifier("{BLOCK UUIDv4}")
let text: [RichText] = [
    "Current time: ",
    .init(string: Date().description, annotations: .bold)
]
let block = UpdateBlock(type: .paragraph(text: text))
notion.blockUpdate(blockId: blockId, value: block) {
    print("Updated: ", $0)
}

Block delete

let blockId = Block.Identifier("{BLOCK UUIDv4}")

notion.blockDelete(blockId: block.id) {
    print("Delete: ", $0)
}

Retrieve a user

let id = User.Identifier("{USER UUIDv4}")
notion.user(userId: id) {
    print($0)
}

List all users

notion.usersList() {
    print($0)
}

Search

Search for pages & databases with a title containing text "Lorem"

notion.search(
    request: .init(
        query: "Lorem"
    )
) {
    print($0)
}

Search for all databases and ignore pages.

notion.search(
    request: .init(
        filter: .database
    )
) {
    print($0)
}

Get all pages & databases

notion.search() {
    print($0)
}

Logging and debugging

NotionSwift provide an internal rudimental logging system to track HTTP traffic. To enable it you need to set a build-in or custom logger handler and decide about log level (.info by default). With .track log level you can see all content of a request. This is useful to track mapping issues between library data models and API.

Example logging configuration:

// This code should be in the ApplicationDelegate

NotionSwiftEnvironment.logHandler = NotionSwift.PrintLogHandler() // uses print command
NotionSwiftEnvironment.logLevel = .trace // show me everything

License

NotionSwift is available under the MIT license. See the LICENSE file for more info.

notionswift's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

notionswift's Issues

Unable to set a page relation during Page Create Request

Hi team!

Am creating a new page in a database, that includes a relation field ("User"). But am receiving this validation error message back from the API:

failure(NotionSwift.NotionClientError.apiError(status: 400, code: "validation_error", message: "body failed validation: body.properties.User.relation[0] should be an object, instead was \"a8073fb6-1fad-4aa8-93fc-31474eb77529\"."))

My original page request properties are set out as follows:

properties: [
                "Name": .init(
                    type: .title([
                        .init(string: "A new session was started")
                    ])
                ),
                "identifier": .init(
                    type: .richText([
                        .init(string: "\(self.user.id?.uuidString ?? "Unknown user")")
                    ])
                ),
                "User": .init(type: .relation([.init("a8073fb61fad4aa893fc31474eb77529")])),
                "Tags": .init(
                    type: .multiSelect([
                        .init(id: nil, name: "Hello", color: nil)
                    ])
                )
            ],

The original request output for the relation field shows as follows:
"User": NotionSwift.WritePageProperty(type: NotionSwift.PagePropertyType.relation([ID:a8073fb6-1fad-4aa8-93fc-31474eb77529]))

I've spent hours trying to resolve it, if the error is on my side, or you need more info to investigate. Do let me know.

Database Query Decode Issue

When calling .databaseQuery, I am seeing the following error when trying to decode the response from the API:

failure(NotionSwift.NotionClientError.decodingError(Swift.DecodingError.typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "next_cursor", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))))
2021-12-02 22:17:45.815971-0500 notion-viewer[8562:5939368] [boringssl] boringssl_metrics_log_metric_block_invoke(144) Failed to log metrics

It looks like in the ListResponse struct, this package defines nextCursor with type Int?. However in the API response from the current version of the Notion API, it is returning a string for this value.

It looks like simply changing this type to String? should fix this. Happy to make a PR to fix this, but wanted to post the issue here first to see what the creator thinks.

OAuth Support

Thanks for what you're doing with this project.

In the README it is stated:

Currently, this library supports only the "internal integration" authorization mode.

I'm not too familiar with OAuth, but from the Notion docs it seemed as though once the OAuth flow has succeeded you are simply given a token which can be used in essentially the same way as a personal token.
Are there differences in how you interact with the API when using OAuth (such that changes would be needed in this package in order to support it)?

Notion login

It would be great if you can add notion login via in-app web to get token automatically

Rename Environment

Could you rename Environment? It conflicts with SwiftUI and gives the error "'Environment' is ambiguous for type lookup in this context" when I try to use @Environment variables.

Adding new row to multi-column database in notion

Is there currently a way with this package to add a new single row to a multi-column database in Notion and map the new values to named columns in the database? Sort of confused on the documentation around this and unsure if it's possible. Thanks for any help, and thanks for creating this great project.

Unable to update database due to missing icons

I want to update the properties on my database, but am unable to because of Notion error 1.

let request = DatabaseUpdateRequest(title: database.title, icon: database.icon, cover: database.cover, properties: updatedProperties)

body validation failed. Fix one: body.icon.emoji should be defined, instead was undefined. body.icon.external should be defined, instead was undefined.

I'm not sure why the icons would matter when updating properties. But anyway, I got the error with a database that had only an icon and no cover. And also on a database with both an icon and cover image.

October updates

Updating SDK to the latest changes in the API:

  • Database objects now contain url
  • Retrieve your token's bot user with GET /v1/users/me
  • Users can now add and update Callout and Quote block types
  • Users can now add Equation Blocks, Embed, Bookmark, and Media Blocks (video, audio, image, file, pdf)

Create page decoding fails for null link property

I'm using pageCreate to create a page inside a database with some properties. Create request succeed, but the response parse fails due to a null link property.

This is the error message that I get:

[ERROR] decoding: valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "properties", intValue: nil), _JSONKey(stringValue: "GCal Link", intValue: nil), CodingKeys(stringValue: "url", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))

On the Notion database, GCal link is a URL property, that is usually empty:

Screenshot 2022-08-15 at 10 53 01

I'm able to reproduce the issue every time, let me know if you need other info!

Support URL property in Page responses

Notion vends a page's URL via the url property in Page object responses.
The Page type in NotionSwift does not seem to support this property however.
As far as I can tell implementing this would mostly just be adding a url property to the Page type.

Reference: Notion documentation for the Page object.

You can verify it is included in responses with NotionSwift trace-level logging enabled.

API request fails when database contains a Double value not Int

Screenshot 2022-05-29 at 12 21 47

Screenshot 2022-05-29 at 12 21 57

When trying to query a database that contains a double value (in the example given: 1,000.50) as opposed to a rounded Integer value (1000.00), the request fails and an "Error 3" is returned.

This happens on Formula property types, number property types and rollup types.

DatabaseFilter with .number

This doesn't seem to make sense:

extension DatabasePropertyFilter {
    public enum NumberCondition {
        case equals(Int)
        case doesNotEqual(Int)
        case greaterThan(Int)
        case lessThan(Int)
        case greaterThanOrEqualTo(Int)
        case lessThanOrEqualTo(Int)
        case isEmpty
        case isNotEmpty
    }
 }

number for Notion is a Decimal and not an integer. Plus, trying to create a filter with (an example Int):

.property(name: "theKey", type: .number(.equals(10000)))

Fails to query at all: NotionSwift.NotionClientError error 1.

Oddly, using the example from your test code, this works:

.property(name: "theKey", type: .number(.greaterThanOrEqualTo(10000)))

However, it still has the problem of being an Int instead of a Decimal

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.