Giter VIP home page Giter VIP logo

notionapi's Introduction

notionapi

GitHub tag (latest SemVer) Go Reference Test

This is a Golang implementation of an API client for the Notion API.

Supported APIs

It supports all APIs of the Notion API version 2022-06-28.

Installation

go get github.com/jomei/notionapi

Usage

First, please follow the Getting Started Guide to obtain an integration token.

Initialization

Import this library and initialize the API client using the obtained integration token.

import "github.com/jomei/notionapi"

client := notionapi.NewClient("your_integration_token")

Calling the API

You can use the methods of the initialized client to call the Notion API. Here is an example of how to retrieve a page:

page, err := client.Page.Get(context.Background(), "your_page_id")
if err != nil {
    // Handle the error
}

notionapi's People

Contributors

ariary avatar chapterjason avatar chirab avatar dxaviud avatar ericz15 avatar evanfuller avatar jomei avatar joshmenden avatar kanata2 avatar kertox662 avatar kyya avatar marcustut avatar mcdoker18 avatar mustafasegf avatar nickysemenza avatar pcarion avatar pierce-m avatar redalaanait avatar ronazst avatar rxrw avatar scottweitzner avatar shayanh avatar shivaji17 avatar vutratenko avatar wh-adwin avatar xzebra avatar yogasw avatar yuyaabo avatar yyewolf avatar zaq1tomo avatar

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  avatar

notionapi's Issues

Support nested compound filters

Per notion reference, the compound filters can be nested. Currently, CompoundFilter is defined as map[FilterOperator][]PropertyFilter, thus only property filters are allowed inside of a compound filter.

It's better to have a Filter interface, and let both CompoundFilter and PropertyFilter to implement the interface.

I can try to do it in the coming days.

Client does not handle 429 errors and Retry-After response header value

The Notion API is rate limited, and it recommends to use the Retry-After response header value if the API returns a 429 error. I don't think it is currently possible to access response headers using this client.

A possible solution is to struct embed http.Response in all of the returned struct types where this is necessary, then update the code accordingly. This makes sure the API does not break.
Google's go-github client does this, for example, so it's not an uncommon pattern. I'll be making a PR for this change.

Edit: after reading more of the code and associated tests, I think it may be too big of a change to embed the full *http.Response. Instead, it may be better for now to just add two new fields, StatusCode and Header, from http.Response.

Edit: PR has been closed, decided to implement a different solution. The new solution is to handle the 429 errors within the notionapi.Client rather than exposing this error to users.

Supports update page's icon and cover

HiHi master, thanks for your SDK!

The official API supports updating the icon and cover of the page. Could you update the SDK to support this function ?

Empty interfaces on some properties types.

While using the lib, I noticed that a bunch of properties have some fields typed with interface{}:

  • DateProperty.Date
  • PeopleProperty.People
  • CheckboxProperty.Checkbox
  • ...

While technically, this doesn't prevent to use the API, it still shifts the work on the library user to handle those fields. Is there a particular reason motivating this choice?

From my understanding so far, the API data structures for these fields are rather straightforward, thus it should be simple to handle those cases.

I don't want to meddle with unwanted PRs, so I thought it'd be best to first discuss things here!

Table children aren't retrieved

See https://developers.notion.com/reference/block#table-blocks

When retrieving a TableBlock, The Children array of the Table element is always empty even if the tableBlock has children (HasChildren=true)

I see in the code that https://github.com/jomei/notionapi/blob/main/block.go#L31:

// NOTE: For blocks with their own children, the Children slice will not be
// populated, instead the HasChildren flag will be true.

Thus my question is: how can I retrieve children for TableBlock ? (table_row children)

Setters for Block interface

It would be great if we could set fields on blocks via the block interface to limit casting + switch-case statements when working with update APIs.

Improve Comments & Documentation

I think we could improve the library by adding some additional comments in parts of the code that connects back to Notion's API documentation.

What might be the highest priority items to tackle first? And are there any commenting style guides I could reference or follow?

I'd love to contribute if possible.

unable to remove the value of select

I can use the following request to remove the value of select.

PATCH https://api.notion.com/v1/pages/:id
{
    "properties": {
        "ProjectType": {
            "select": null
        }
    }
}

But when used the notionapi pkg to send the request, I cann't set the select to nil. Because the type of select is notionapi.Option instead of *notionapi.Option.

type SelectProperty struct {
	ID     ObjectID           `json:"id,omitempty"`
	Type   PropertyType `json:"type,omitempty"`
	Select Option            `json:"select"`
}

when I used notionapi.Option{} as the value of Select, I got the following errer response:

body.properties.ProjectType.select.id should be defined, instead was `undefined`.
body.properties.ProjectType.select.name should be populated, instead was `""`.

How can I remove the value of select propery?

Is SearchFilter necessary?

First of all,
thank you for the repo! I am very much enjoying it.

Currently, I am testing some workloads with Search.Do function that requires a SearchFilter.

Notion API does not require for us to specify any filter (basically omit,) but I think there must be other reasons you have enforced it not to be nil. However, the objects we retrieve from the Search.Do function do provide the ObjectType, which could hint us the type of the object, meaning that we could possibly skip the SearchFilter if that is nil.

So... if there's any chance, could I post PR with the following and the some tests:

  1. SearchFilter can be nil
  2. Search.Do() will no longer reference nil pointer that were supposed* to be a SearchFilter instance.

But as always, I would like to learn the reason.
Thank you!

Proposal: Define a basic block type with the common fields

The Blocks returned by the API all have some common fields like the type, ID, etc. A basic block type can be defined which has these fields and then embedded in the other blocks. This would reduce the duplication of these fields in the different block types.

For example:

type basicBlock struct {
	Object         ObjectType `json:"object"`
	ID             BlockID    `json:"id,omitempty"`
	Type           BlockType  `json:"type"`
	CreatedTime    *time.Time `json:"created_time,omitempty"`
	LastEditedTime *time.Time `json:"last_edited_time,omitempty"`
	HasChildren    bool       `json:"has_children,omitempty"`
        Archived       bool       `json:"archived,omitempty"`
}

...
type ParagraphBlock struct {
         basicBlock
         Paragraph      Paragraph  `json:"paragraph"`
}

The idea being similar to https://play.golang.org/p/BTwPIwzuFJ8
Not sure how compatible this is with #10

bug: resp body not close

Hi, firstly thanks for the code!
When I checked the client code, I found that all the response body from http client is not closed. Typically, there should be a line of
defer resp.Body.Close()

Otherwise it causes resource leak.

Authentication Client does not work

The CreateToken endpoint uses the same underlying impl request helper function as all other API endpoints in this package.

This implementation is hard-coded to use the Bearer prefix when making requests.

Notion's public docs for this endpoint define that it uses the Basic prefix, where the token is the base64-encoded concatenation of an OAuth app's client ID and secret.

Two possible fixes are:

  1. Band-aid fix by using the Basic prefix for this endpoint only, and expect library users to pass in the base64-encoded token.
  2. Implement a richer Token type that can support both Bearer and Basic cases, the latter of which could include struct fields for ID + Secret, and implement the base64 encoding in this library.

Block's children not being decoded

The JSON decoder doesn't know how to parse the type []Block of children attributes.

type ListItem struct {
	Text     []RichText `json:"text"`
	Children []Block    `json:"children,omitempty"`
}
...
type NumberedListItemBlock struct {
	Object           ObjectType `json:"object"`
	ID               BlockID    `json:"id,omitempty"`
	Type             BlockType  `json:"type"`
	CreatedTime      *time.Time `json:"created_time,omitempty"`
	LastEditedTime   *time.Time `json:"last_edited_time,omitempty"`
	HasChildren      bool       `json:"has_children,omitempty"`
	NumberedListItem ListItem   `json:"numbered_list_item"`
}

This is due to Block beign an interface and JSON can't resolve dynamically the Block type.

One way to solve this is creating a Block struct with the common attributes, decoding it first and then resolving the concrete type dynamically.

Missing parent field in Block interface

A block returned by the Notion API contains an object field parent, which points to either a parent page reference or parent block reference in the case of nested children.

For example:

{
  "object": "block",
  "id": "fbc4a8e8-302e-4258-a8eb-6a5f5066ddfe",
  "parent": {
    "type": "page_id",
    "page_id": "bdd526b3-fb3e-44c7-ac77-797023bcb73f"
  },
  ...
}

or

{
  "object": "block",
  "id": "7d5aea6a-2c98-4496-a81d-7d1bfbe7fb6b",
  "parent": {
    "type": "block_id",
    "block_id": "fbc4a8e8-302e-4258-a8eb-6a5f5066ddfe"
  },
  ...
}

It would be great if the Block interface had a function to fetch the parent, perhaps something like

type Parent interface {
  GetType() string
  GetID() string
}

type Block interface {
  ...
  GetParent() Parent
}

Generate mock client?

Hello!

What do you think about a default mock for the Notion API client? We have that in our fork here (still work in progress). With that, it's quicker to get started with unit tests.

Unset date property

Currently, it isn't possible to unset a date property.
Making it a pointer works.

Date DateObject `json:"date"`

I've created a small workaround that works:

type EmptyDateProperty struct {
	ID   notionapi.ObjectID     `json:"id,omitempty"`
	Type notionapi.PropertyType `json:"type,omitempty"`
	Date *notionapi.DateObject  `json:"date"`
}

func (p EmptyDateProperty) GetType() notionapi.PropertyType {
	return p.Type
}

// [...]

updates["Completed At"] = &EmptyDateProperty{
    ID:   completedAtProperty.ID,
    Type: completedAtProperty.Type,
    Date: nil,
}

// [...]

Interface for getting RichText from various blocks

So many blocks have the RichText field, so when retrieving blocks and getting their rich text, clients have to do

switch t := blockWithRichText.(type) {
  case *notionapi.Heading1:
    return t.Heading1.RichText
  case *notionapi.Heading2:
    return t.Heading2.RichText

It would be so much better to do

richText := t.GetRichText()

Maybe an interface such as

type BlockWithRichText interface {
  Block
  GetRichText() []RichText
}

would work?

Is there a way to update code block caption?

BlockUpdateRequest struct has a field Code but the ``Codestruct does not have Caption` field. rather it is the `CodeBlock` that has the `Caption` field.

My question is: is there a way to update CodeBlock instead of Code thus making the caption update possible?

Query database: sort by page timestamps doesn't work

Thanks for developing this SDK within a few weeks after the public beta launch!

Notion doc:

Sorts are similar to the sorts provided in the Notion UI. Sorts operate on database properties or page timestamps and can be combined.

It seems that Property Field of SortObject should be omitted in case of an empty value, to allow sort by page timestamps

Otherwise notion API returns:

{
    "object": "error",
    "status": 400,
    "code": "validation_error",
    "message": "Could not find sort property with name or id: "
}

Add Read Support for Status Properties

I just opened this PR for adding support for reading status property configs, which was core to my use case.

Even if it's accepted, I'm still unclear on the best way to add support for parsing the status property type into a StatusPropertyConfig while also preventing the StatusPropertyConfig from being used as an input for the DatabaseClient.Create() and DatabaseClient.Update() methods.

Perhaps we could add a methods to DataBaseUpdateRequest and DatabaseCreateRequest that would remove any incompatible PropertyConfigs assigned to them?

I've not investigated this thoroughly, but it doesn't appear to me that the .Update() method has any logic in place to prevent other invalid PropertyConfigs, e.g. formula from being passed in as values.

Would love to hear your thoughts!

"This API is deprecated." When fetching a db with no id.

When fetching a Database with an empty ID I get a misleading error "This API is deprecated.".

	client := notionapi.NewClient(notionapi.Token("mytoken"))

	db, err := client.Database.Get(context.TODO(), notionapi.DatabaseID(""))
	if err != nil {
		panic(err.Error())
	}
panic: This API is deprecated.

Util functions for RichText properties

I often just want to get the plain text from a property like the TitleProperty to print it in log or somewhere else.

What about adding some util functions which concats the []RichText and returns it as string.

Something like:

func (p TitleProperty) GetPlainText() string {
	builder := strings.Builder{}

	for _, title := range p.Title {
		builder.WriteString(title.PlainText)
		builder.WriteString(" ")
	}

	return strings.TrimSpace(builder.String())
}

We could also add something like GetMarkdown or getHTML, but with some limitations like the color annotation in markdown wouldn't work.

Would affect the following properties:

  • TitleProperty
  • RichTextProperty
  • TextProperty

WDYT?

Array Rollup causes JSON unmarshal error.

Having an array type rollup causes unmarshalling to give an error:
json: cannot unmarshal object into Go struct field Page.properties of type notionapi.Property
This is because it does not know how to unmarshal into an interface. There is a TODO listed there in the property.go file.

This error happens when the encounters something like this: https://go.dev/play/p/UgSBjohh-Cy

Interface to unify Pdf, FileBlock, Image

Currently these share the same underlying structure, though I guess it's not guaranteed to stay that way. To reduce switch-case statements in client code, it would be helpful to define an interface to get these common fields, maybe

type DownloadableFileBlock interface {
  Block
  GetURL() string
  GetExpiryTime() *time.Time
} 

Calling Database Query without specifying DatabaseQueryRequest fails

var ptr *notion.DatabaseQueryRequest
response, err := client.Database.Query(context.Background(), notion.DatabaseID(id), ptr)

As seen in the snippet above, if a pointer to empty struct or nil is passed, the module encounters error.

image

I have confirmed that the POST /v1/databases/<id>/query works without supplying any of the filter, sort, cursor or page size.

Block appendChildren Will return a error, but request is success

example code

	key := "token token token"

	client := notionapi.NewClient(notionapi.Token(key))

	childrenvv := notionapi.ParagraphBlock{
		Object: notionapi.ObjectTypeBlock,
		Type:   notionapi.BlockTypeParagraph,
		Paragraph: notionapi.Paragraph{
			Text: []notionapi.RichText{
				{
					Type: notionapi.ObjectTypeText,
					Text: notionapi.Text{
						Content: "AAAAAA",
					},
					Annotations: &notionapi.Annotations{
						Bold:          true,
						Italic:        false,
						Strikethrough: false,
						Underline:     false,
						Code:          false,
						Color:         "",
					},
				},
				{
					Type: notionapi.ObjectTypeText,
					Text: notionapi.Text{
						Content: " BBBBB",
					},
					Annotations: &notionapi.Annotations{
						Bold:          false,
						Italic:        false,
						Strikethrough: false,
						Underline:     false,
						Code:          false,
						Color:         "",
					},
				},
			},
		},
	}

	req := notionapi.AppendBlockChildrenRequest{Children: []notionapi.Block{childrenvv}}

	appendChildren, err := client.Block.AppendChildren(context.TODO(), "blockId(is a page)", &req)
	if err != nil {
		panic(err)
	}

	fmt.Println(appendChildren)

get error : interface conversion: interface {} is nil, not string in github.com/jomei/[email protected]/block.go:537

Generating structs based on the typescript types from the official JS sdk

After going back and forth on #9, it feels like making sure the API reference is properly implemented is a quite meticulous task that is quite error prone and may introduce vicious bugs that will be hard to track down.

The official JS SDK has all the API types properly defined in Typescript, which could be used to generate those structs: https://github.com/makenotion/notion-sdk-js/blob/main/src/api-types.ts

An example on how to achieve that can be found in gopls, which uses that approach to handle the structs that deals with the Language Server Protocol: https://github.com/golang/tools/tree/master/internal/lsp/protocol/typescript

I may give it a try in the upcoming days, I'll post here if I do!

Support for editing BasicBlock in every block

Is it possible to add support for returning pointer to BasicBlock which is part of every block type? This would help editing the basic block field without needing to type cast Block interface to concrete block type object.
Something similar to this

type Block interface {
	...
        ...
        ...
	GetBasicBlock() *BasicBlock.  <-  New addition
}
...
...
...
func (b *BasicBlock) GetBasicBlock() *BasicBlock {
	return b
}

If any client wants to edit something from BasicBlock which is common to all blocks, the client would always need to type cast the block interface to a particular block type and edit the BasicBlock info even when the client is not making any change specific to given block type.

Database Query using PropertyFilter on Text returns error

Hi, I'm trying to query a database based on the property Name:
image

The equivalent curl request gives a successful response:

➜ curl -X POST 'https://api.notion.com/v1/databases/'$DB_ID'/query' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Notion-Version: 2022-02-22' \
  -H "Content-Type: application/json" \
  --data '{
  "filter": {
    "property": "Name",
    "title": {
      "equals": "May 2022"
    }
  }
}'

However when using the package:

	dbQueryReq := &notionapi.DatabaseQueryRequest{
		PropertyFilter: &notionapi.PropertyFilter{
			Property: "Name",
			Text: &notionapi.TextFilterCondition{
				Equals: today.Format("Jan 2006"),
			},
		},
	}
	d, err := t.client.Database.Query(ctx, notionapi.DatabaseID(dbId), dbQueryReq)

Returns the error

panic: body failed validation. Fix one:
body.filter.or should be defined, instead was `undefined`.
body.filter.title should be defined, instead was `undefined`.

...

Looks like the Text field in PropertyFilter needs to have it's json_tag updated to use rich_text ?

Common interface for Block, Page, and Database

It would be very helpful to abstract these objects away in a comment interface, as currently in our code we use a lot of switch/case such as

switch obj.Type{
  case "block":
    return obj.CreatedAt
  case "page":
    return obj.CreatedAt
}

It would be much nicer to be able to do

obj.GetCreatedAt()

simplifying the client code greatly. It seems Blocks have a common Block interface, I am wondering if we can create a common interface shared between Blocks, Pages, and Databases to access these common fields?

Need way to retrieve Page Content

According to this Notion Wiki page, Page also uses retrieve block children endpoint.

Currently, BlockService.GetChildren() only uses BlockID type for block_id parameter, and PageService doesn't have GetChildren() method. Then, How can I retrieve Page Content? Is there any way to do this?

I think adding GetChildren() method to PageService may make this work.

Omit the unnecessary fields in toggle block

The ToggleBlock specifies RichText and Text fields which are not documented in the Notion API. I suppose these were left from earlier versions.

Practically, these fields should not be causing any errors as they're serialised only when they're present (i.e. omitempty). However, the RichText field misses the omitempty, causing error responses form Notion.

Would be great if the unnecessary fields can be cleaned up - if that's too much work just adding an omitempty tag to ToggleBlock.RichText would do the job too.

Ref. https://github.com/jomei/notionapi/blob/main/block.go#L352.

PS, not sure how you manage contributions here, but given the right permissions I can open up a PR as well.

Many thanks.

Failed to "uncheck" a todo block using update api

Bug:
It seems that updating a ToDoBlock with Todo properties check equals to false does not uncheck the box if it has been previously checked.

Test case:

  1. Create a page with a to do block
  2. Check it
  3. Try to update it using BlockUpdateRequest with a ToDo object with check properties with the false value

-> The box remains checked even after the update.

Conversely, if the box has not been checked and you make an update with check at true, the box changes and become checked.

Support for editing `BasicBlock` in every block

Is it possible to add support for editing BasicBlock which is part of every block type?
Something similar to this

type Block interface {
	...
        ...
        ...
	GetBasicBlock() *BasicBlock.  <-  New addition
}
...
...
...
func (b *BasicBlock) GetBasicBlock() *BasicBlock {
	return b
}

If any client wants to edit something from BasicBlock which is common to all blocks, the client would always need to type cast the block interface to a particular block type and edit the BasicBlock info even when the client is not making any change specific to given block type.

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.