notion 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +21 -0
- data/README.md +196 -0
- data/lib/notion_api.rb +5 -0
- data/lib/notion_api/blocks.rb +984 -0
- data/lib/notion_api/client.rb +16 -0
- data/lib/notion_api/core.rb +217 -0
- data/lib/notion_api/markdown.rb +7 -0
- data/lib/notion_api/utils.rb +518 -0
- data/lib/notion_api/version.rb +3 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b3ebde6eb43b792a313801584c8043c579e9fd356427df080a14c265e28cf2db
|
4
|
+
data.tar.gz: 9bf0242abe3ad4c15cebbb9a1cdd65ae64b6ec76a1d8e872836a90b5c2ec9404
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f0b872d9f1eb4f88f05e4d8e41959291dc4b86bdb0be46582d196de095f174907b78c165c35140c7bc68b41b4cd6b7b7098ae23a3502d8adecc488e9185c03a
|
7
|
+
data.tar.gz: 8c69f7a02a7374a69590b4b1cdb92767557efaf7f56e5fa7fe3f8450f78c19e4fa9e37b5db8c46e9aef6661dda8dfc56692baeff57912769f052e831af9ae594
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2020 Dan Murphy
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
# Unofficial Notion Client for Ruby.
|
2
|
+
[![Build Status](https://travis-ci.com/danmurphy1217/notion-ruby.svg?branch=master)](https://travis-ci.com/danmurphy1217/notion-ruby) [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop-hq/rubocop)
|
3
|
+
|
4
|
+
## Getting Started
|
5
|
+
To get started using package, you'll first need to retrieve your token_v2 credentials by signing into Notion online, navigating to the developer tools, inspecting the cookies, and finding the value associated with the **token_v2** key.
|
6
|
+
|
7
|
+
From here, you can instantiate the Notion Client with the following code:
|
8
|
+
```ruby
|
9
|
+
>>> @client = NotionAPI::Client.new("<insert_v2_token_here>")
|
10
|
+
```
|
11
|
+
## Retrieving a Page
|
12
|
+
A typical starting point is the `get_page` method, which returns a Notion Page Block. The `get_page` method accepts the ID (formatted or not) or the URL of the page:
|
13
|
+
1. URL → https://www.notion.so/danmurphy/TEST-PAGE-d2ce338f19e847f586bd17679f490e66
|
14
|
+
2. ID → d2ce338f19e847f586bd17679f490e66
|
15
|
+
3. Formatted ID → d2ce338f-19e8-47f5-86bd-17679f490e66
|
16
|
+
```ruby
|
17
|
+
>>> @client.get_page("https://www.notion.so/danmurphy/TEST-PAGE-d2ce338f19e847f586bd17679f490e66")
|
18
|
+
>>> @client.get_page("d2ce338f19e847f586bd17679f490e66")
|
19
|
+
>>> @client.get_page("d2ce338f-19e8-47f5-86bd-17679f490e66")
|
20
|
+
```
|
21
|
+
All three of these will return the same block instance:
|
22
|
+
```ruby
|
23
|
+
#<NotionAPI::PageBlock id="d2ce338f-19e8-47f5-86bd-17679f490e66" title="TEST" parent_id="<omitted>">
|
24
|
+
```
|
25
|
+
The following attributes can be read from any block class instance:
|
26
|
+
1. `id`: the ID associated with the block.
|
27
|
+
2. `title`: the title associated with the block.
|
28
|
+
3. `parent_id`: the parent ID of the block.
|
29
|
+
4. `type`: the type of the block.
|
30
|
+
|
31
|
+
## Retrieving a Block within the Page
|
32
|
+
Now that you have retrieved a Notion Page, you have full access to the blocks on that page. You can retrieve a specific block or collection view, retrieve all children IDs (array of children IDs), or retrieve all children (array of children class instances).
|
33
|
+
|
34
|
+
### Get a Block
|
35
|
+
To retrieve a specific block, you can use the `get_block` method. This method accepts the ID of the block (formatted or not), and will return the block as an instantiated class instance:
|
36
|
+
```ruby
|
37
|
+
@page = @client.get_page("https://www.notion.so/danmurphy/TEST-PAGE-d2ce338f19e847f586bd17679f490e66")
|
38
|
+
@page.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
39
|
+
#<TextBlock id="2cbbe0bf-34cd-409b-9162-64284b33e526" title="TEST" parent_id="d2ce338f-19e8-47f5-86bd-17679f490e66">
|
40
|
+
```
|
41
|
+
Any Notion Block has access to the following methods:
|
42
|
+
1. `title=` → change the title of a block.
|
43
|
+
```ruby
|
44
|
+
>>> @block = @client.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
45
|
+
>>> @block.title # get the current title...
|
46
|
+
"TEST"
|
47
|
+
>>> @block.title= "New Title Here" # lets update it...
|
48
|
+
>>> @block.title
|
49
|
+
"New Title Here"
|
50
|
+
```
|
51
|
+
2. `convert` → convert a block to a different type.
|
52
|
+
```ruby
|
53
|
+
>>> @block = @client.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
54
|
+
>>> @block.type
|
55
|
+
"text"
|
56
|
+
>>> @new_block = @block.convert(NotionAPI::CalloutBlock)
|
57
|
+
>>> @new_block.type
|
58
|
+
"callout"
|
59
|
+
>>> @new_block # new class instance returned...
|
60
|
+
#<NotionAPI::CalloutBlock:0x00007ffb75b19ea0 id="2cbbe0bf-34cd-409b-9162-64284b33e526" title="New Title Here" parent_id="d2ce338f-19e8-47f5-86bd-17679f490e66">
|
61
|
+
```
|
62
|
+
3. `duplicate`→ duplicate the current block.
|
63
|
+
```ruby
|
64
|
+
>>> @block = @client.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
65
|
+
>>> @block.duplicate # block is duplicated and placed directly after the current block
|
66
|
+
>>> @block.duplicate("f13da22b-9012-4c49-ac41-6b7f97bd519e") # the duplicated block is placed after 'f13da22b-9012-4c49-ac41-6b7f97bd519e'
|
67
|
+
```
|
68
|
+
4. `move` → move a block to another location.
|
69
|
+
```ruby
|
70
|
+
>>> @block = @client.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
71
|
+
>>> @target_block = @client.get_block("c3ce468f-11e3-48g5-87be-27679g491e66")
|
72
|
+
>>> @block.move(@target_block) # @block moved to **after** @target_block
|
73
|
+
>>> @block.move(@target_block, "before") # @block moved to **before** @target_block
|
74
|
+
```
|
75
|
+
### Get a Collection View - Table
|
76
|
+
To retrieve a collection, you use the `get_collection` method. This method is designed to work with Table collections, but the codebase is actively being updated to support others:
|
77
|
+
```ruby
|
78
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/TEST-PAGE-d2ce338f19e847f586bd17679f490e66")
|
79
|
+
>>> @page.get_collection("34d03794-ecdd-e42d-bb76-7e4aa77b6503")
|
80
|
+
#<NotionAPI::CollectionView:0x00007fecd8859770 @id="34d03794-ecdd-e42d-bb76-7e4aa77b6503", @title="Car Data", @parent_id="9c50a7b3-9ad7-4f2b-aa08-b3c95f1f19e7", @collection_id="5ea0fa7c-00cd-4ee0-1915-8b5c423f8f3a", @view_id="5fdb08da-0732-49dc-d0c3-2e31fccca73a">
|
81
|
+
```
|
82
|
+
Any Notion Block has access to the following methods:
|
83
|
+
1. `row_ids` → retrieve the IDs associated with each row.
|
84
|
+
```ruby
|
85
|
+
>>> @collection = @page.get_collection("34d03794-ecdd-e42d-bb76-7e4aa77b6503")
|
86
|
+
>>> @collection.row_ids
|
87
|
+
ent.rb
|
88
|
+
["785f4e24-e489-a316-50cf-b0b100c6673a", "78642d95-da23-744c-b084-46d039927bba", "96dff83c-6961-894c-39c2-c2c8bfcbfa90", "87ae8ae7-5518-fbe1-748e-eb690c707fac",..., "5a50bdd4-69c5-0708-5093-b135676e83c1", "ff9b8b89-1fed-f955-4afa-5a071198b0ee", "721fe76a-9e3c-d348-8324-994c95d77b2e"]
|
89
|
+
```
|
90
|
+
2. `rows` → retrieve each Row, returned as an array of TableRowInstance classes.
|
91
|
+
```ruby
|
92
|
+
>>> @collection = @page.get_collection("34d03794-ecdd-e42d-bb76-7e4aa77b6503")
|
93
|
+
>>> @collection.rows
|
94
|
+
#<NotionAPI::CollectionViewRow:0x00007ffecca82078 @id="785f4e24-e489-a316-50cf-b0b100c6673a", @parent_id="9c50a7b3-9ad7-4f2b-aa08-b3c95f1f19e7", @collection_id="5ea0fa7c-00cd-4ee0-1915-8b5c423f8f3a", @view_id="5fdb08da-0732-49dc-d0c3-2e31fccca73a">,..., #<NotionAPI::CollectionViewRow:0x00007ffecca81998 @id="fbf44f93-52ee-0e88-262a-94982ffb3fb2", @parent_id="9c50a7b3-9ad7-4f2b-aa08-b3c95f1f19e7", @collection_id="5ea0fa7c-00cd-4ee0-1915-8b5c423f8f3a", @view_id="5fdb08da-0732-49dc-d0c3-2e31fccca73a">]
|
95
|
+
```
|
96
|
+
3. `row("<row_id>")` → retrieve a specific row.
|
97
|
+
```ruby
|
98
|
+
>>> @collection = @page.get_collection("34d03794-ecdd-e42d-bb76-7e4aa77b6503")
|
99
|
+
>>> @collection.row("f1c7077f-44a9-113d-a156-90ab6880c3e2")
|
100
|
+
{"age"=>[9], "vin"=>["1C6SRFLT1MN591852"], "body"=>["4D Crew Cab"], "fuel"=>["Gasoline"], "make"=>["Ram"], "msrp"=>[64935], "year"=>[2021], "model"=>[1500], "price"=>[59688], "stock"=>["21R14"], "dealerid"=>["MP2964D"], "colour"=>["Bright White Clearcoat"], "engine"=>["HEMI 5.7L V8 Multi Displacement VVT"], "photos"=>["http://vehicle-photos-published.vauto.com/d0/c2/dd/8b-1307-4c67-8d31-5a301764b875/image-1.jpg"], "series"=>["Rebel"], "newused"=>["N"], "city_mpg"=>[""],...,"engine_cylinder_ct"=>[8], "engine_displacement"=>[5.7], "photos_last_modified_date"=>["11/13/2020 8:16:56 AM"]}
|
101
|
+
```
|
102
|
+
|
103
|
+
## Creating New Blocks
|
104
|
+
To create a new block, you have a few options:
|
105
|
+
### Create a block whose parent is the page
|
106
|
+
If you want to create a new block whose parent ID is the **page**, call the `create` method on the PageBlock instance.
|
107
|
+
1. `@page.create("<type_of_block", "title of block")` → create a new block at the end of the page.
|
108
|
+
```ruby
|
109
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
110
|
+
>>> @page.create(NotionAPI::TextBlock, "Hiya!")
|
111
|
+
#<NotionAPI::TextBlock:0x00007fecd4459770 **omitted**>
|
112
|
+
```
|
113
|
+
2. `@page.create("<type_of_block", "title of block", "target_block_id")` → create a new block after the target block.
|
114
|
+
```ruby
|
115
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
116
|
+
>>> @page.create(NotionAPI::TextBlock, "Hiya!", "ee0a6531-44cd-439f-a68c-1bdccbebfc8a")
|
117
|
+
#<NotionAPI::TextBlock:0x00007fecd8859770 **omitted**>
|
118
|
+
```
|
119
|
+
3. `@page.create("<type_of_block"), "title of block", "target_block_id", "before/after")` → create a new block after or before the target block.
|
120
|
+
```ruby
|
121
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
122
|
+
>>> @page.create(NotionAPI::TextBlock, "Hiya!", "ee0a6531-44cd-439f-a68c-1bdccbebfc8a", "before")
|
123
|
+
#<NotionAPI::TextBlock:0x00007fecd8859880 **omitted**>
|
124
|
+
```
|
125
|
+
### Create a block whose parent is another block
|
126
|
+
If you want to create a nested block whose parent ID is **another block**, call the `create` method on that block.
|
127
|
+
1. `@block.create("<type_of_block", "title of block")` → create a new nested block whose parent ID is @block.id
|
128
|
+
```ruby
|
129
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
130
|
+
>>> @block = @page.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
131
|
+
>>> @block.create(NotionAPI::TextBlock, "Hiya!") # create a nested text block
|
132
|
+
#<NotionAPI::TextBlock:0x00007fecd8861780 **omitted**>
|
133
|
+
```
|
134
|
+
2. `@block.create("<type_of_block", "title of block", "target_block")` → create a new nested block whose parent ID is @block.id and whose location is after the target block
|
135
|
+
```ruby
|
136
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
137
|
+
>>> @block = @page.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
138
|
+
>>> @block.create(NotionAPI::TextBlock, "Hiya!" "ae3d1c60-b9d1-0ac0-0fff-16d3fc8907a2") # create a nested text block after a specific child
|
139
|
+
#<NotionAPI::TextBlock:0x00007fecd8859781 **omitted**>
|
140
|
+
```
|
141
|
+
3. `@block.create("<type_of_block", "title of block", "target_block", "before/after")` → reate a new nested block whose parent ID is @block.id and whose location is before the target block
|
142
|
+
```ruby
|
143
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
144
|
+
>>> @block = @page.get_block("2cbbe0bf-34cd-409b-9162-64284b33e526")
|
145
|
+
>>> @block.create(NotionAPI::TextBlock, "Hiya!" "ae3d1c60-b9d1-0ac0-0fff-16d3fc8907a2", "before") # create a nested text block before a specific child
|
146
|
+
#<NotionAPI::TextBlock:0x00007fecd8859781 **omitted**>
|
147
|
+
```
|
148
|
+
The simplest way to describe this: the parent ID of the created block is the ID of the block the `create` method is invoked on. If the `create` method is invoked on a **PageBlock**, the block is a child of that page. If the `create` method is invoked on a block within the page, the block is a child of that block.
|
149
|
+
|
150
|
+
** NOTE: Notion only supports 'nesting' certain block types. If you try to nest a block that cannot be nested, it will fail. **
|
151
|
+
## Creating New Collections
|
152
|
+
Let's say we have the following JSON data:
|
153
|
+
```json
|
154
|
+
[
|
155
|
+
{
|
156
|
+
"emoji": "😀",
|
157
|
+
"description": "grinning face",
|
158
|
+
"category": "Smileys & Emotion",
|
159
|
+
"aliases": ["grinning"],
|
160
|
+
"tags": ["smile", "happy"],
|
161
|
+
"unicode_version": "6.1",
|
162
|
+
"ios_version": "6.0"
|
163
|
+
},
|
164
|
+
{
|
165
|
+
"emoji": "😃",
|
166
|
+
"description": "grinning face with big eyes",
|
167
|
+
"category": "Smileys & Emotion",
|
168
|
+
"aliases": ["smiley"],
|
169
|
+
"tags": ["happy", "joy", "haha"],
|
170
|
+
"unicode_version": "6.0",
|
171
|
+
"ios_version": "6.0"
|
172
|
+
}
|
173
|
+
]
|
174
|
+
```
|
175
|
+
A new collection containing this data is created with the following code:
|
176
|
+
```ruby
|
177
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
178
|
+
>>> @page.create_collection("table", "title for table", JSON.parse(File.read("./path/to/emoji_json_data.json")))
|
179
|
+
```
|
180
|
+
Additionally, say you already have a Table and want to add a new row with it containing the following data:
|
181
|
+
```ruby
|
182
|
+
{
|
183
|
+
"emoji": "😉",
|
184
|
+
"description": "winking face",
|
185
|
+
"category": "Smileys & Emotion",
|
186
|
+
"aliases": ["wink"],
|
187
|
+
"tags": ["flirt"],
|
188
|
+
"unicode_version": "6.0",
|
189
|
+
"ios_version": "6.0"
|
190
|
+
}
|
191
|
+
```
|
192
|
+
```ruby
|
193
|
+
>>> @page = @client.get_page("https://www.notion.so/danmurphy/Notion-API-Testing-66447bc817f044bc81ed3cf4802e9b00")
|
194
|
+
>>> @collection = @page.get_collection("f1664a99-165b-49cc-811c-84f37655908a")
|
195
|
+
>>> @collection.add_row(JSON.parse(File.read("path/to/new_emoji_row.json")))
|
196
|
+
```
|
data/lib/notion_api.rb
ADDED
@@ -0,0 +1,984 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'core'
|
4
|
+
require 'httparty'
|
5
|
+
|
6
|
+
module NotionAPI
|
7
|
+
# Base Template for all blocks. Inherits core methods from the Block class defined in block.rb
|
8
|
+
class BlockTemplate < Core
|
9
|
+
include Utils
|
10
|
+
|
11
|
+
attr_reader :id, :title, :parent_id
|
12
|
+
|
13
|
+
def initialize(id, title, parent_id)
|
14
|
+
@id = id
|
15
|
+
@title = title
|
16
|
+
@parent_id = parent_id
|
17
|
+
end
|
18
|
+
|
19
|
+
def title=(new_title)
|
20
|
+
# ! Change the title of a block.
|
21
|
+
# ! new_title -> new title for the block : ``str``
|
22
|
+
request_id = extract_id(SecureRandom.hex(16))
|
23
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
24
|
+
space_id = extract_id(SecureRandom.hex(16))
|
25
|
+
update_title(new_title.to_s, request_id, transaction_id, space_id)
|
26
|
+
@title = new_title
|
27
|
+
end
|
28
|
+
|
29
|
+
def convert(block_class_to_convert_to)
|
30
|
+
# ! convert a block from its current type to another.
|
31
|
+
# ! block_class_to_convert_to -> the type of block to convert to : ``cls``
|
32
|
+
if type == block_class_to_convert_to.notion_type
|
33
|
+
# if converting to same type, skip and return self
|
34
|
+
self
|
35
|
+
else
|
36
|
+
# setup cookies, headers, and grab/create static vars for request
|
37
|
+
cookies = Core.options['cookies']
|
38
|
+
headers = Core.options['headers']
|
39
|
+
request_url = URLS[:UPDATE_BLOCK]
|
40
|
+
|
41
|
+
# set random IDs for request
|
42
|
+
request_id = extract_id(SecureRandom.hex(16))
|
43
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
44
|
+
space_id = extract_id(SecureRandom.hex(16))
|
45
|
+
request_ids = {
|
46
|
+
request_id: request_id,
|
47
|
+
transaction_id: transaction_id,
|
48
|
+
space_id: space_id
|
49
|
+
}
|
50
|
+
|
51
|
+
# build hash's that contain the operations to send to Notions backend
|
52
|
+
convert_type_hash = Utils::BlockComponents.convert_type(@id, block_class_to_convert_to)
|
53
|
+
last_edited_time_parent_hash = Utils::BlockComponents.last_edited_time(@parent_id)
|
54
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(@id)
|
55
|
+
|
56
|
+
operations = [
|
57
|
+
convert_type_hash,
|
58
|
+
last_edited_time_parent_hash,
|
59
|
+
last_edited_time_child_hash
|
60
|
+
]
|
61
|
+
|
62
|
+
request_body = build_payload(operations, request_ids)
|
63
|
+
response = HTTParty.post(
|
64
|
+
request_url,
|
65
|
+
body: request_body.to_json,
|
66
|
+
cookies: cookies,
|
67
|
+
headers: headers
|
68
|
+
)
|
69
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
70
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
71
|
+
|
72
|
+
block_class_to_convert_to.new(@id, @title, @parent_id)
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def duplicate(target_block = nil)
|
78
|
+
# ! duplicate the block that this method is invoked upon.
|
79
|
+
# ! target_block -> the block to place the duplicated block after. Can be any valid Block ID! : ``str``
|
80
|
+
cookies = Core.options['cookies']
|
81
|
+
headers = Core.options['headers']
|
82
|
+
request_url = URLS[:UPDATE_BLOCK]
|
83
|
+
|
84
|
+
new_block_id = extract_id(SecureRandom.hex(16))
|
85
|
+
request_id = extract_id(SecureRandom.hex(16))
|
86
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
87
|
+
space_id = extract_id(SecureRandom.hex(16))
|
88
|
+
|
89
|
+
root_children = children_ids(@id)
|
90
|
+
sub_children = []
|
91
|
+
root_children.each { |root_id| sub_children.push(children_ids(root_id)) }
|
92
|
+
|
93
|
+
request_ids = {
|
94
|
+
request_id: request_id,
|
95
|
+
transaction_id: transaction_id,
|
96
|
+
space_id: space_id
|
97
|
+
}
|
98
|
+
body = {
|
99
|
+
pageId: @id,
|
100
|
+
chunkNumber: 0,
|
101
|
+
limit: 100,
|
102
|
+
verticalColumns: false
|
103
|
+
}
|
104
|
+
|
105
|
+
user_notion_id = get_notion_id(body)
|
106
|
+
|
107
|
+
block = target_block ? get(target_block) : self # allows dev to place block anywhere!
|
108
|
+
|
109
|
+
duplicate_hash = Utils::BlockComponents.duplicate(type, @title, block.id, new_block_id, user_notion_id, root_children)
|
110
|
+
set_parent_alive_hash = Utils::BlockComponents.set_parent_to_alive(block.parent_id, new_block_id)
|
111
|
+
block_location_hash = Utils::BlockComponents.block_location_add(block.parent_id, block.id, new_block_id, target_block, 'listAfter')
|
112
|
+
last_edited_time_parent_hash = Utils::BlockComponents.last_edited_time(block.parent_id)
|
113
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(block.id)
|
114
|
+
|
115
|
+
operations = [
|
116
|
+
duplicate_hash,
|
117
|
+
set_parent_alive_hash,
|
118
|
+
block_location_hash,
|
119
|
+
last_edited_time_parent_hash,
|
120
|
+
last_edited_time_child_hash
|
121
|
+
]
|
122
|
+
|
123
|
+
request_body = build_payload(operations, request_ids)
|
124
|
+
response = HTTParty.post(
|
125
|
+
request_url,
|
126
|
+
body: request_body.to_json,
|
127
|
+
cookies: cookies,
|
128
|
+
headers: headers
|
129
|
+
)
|
130
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
131
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
132
|
+
|
133
|
+
class_to_return = NotionAPI.const_get(Classes.select { |cls| NotionAPI.const_get(cls).notion_type == type }.join.to_s)
|
134
|
+
class_to_return.new(new_block_id, @title, block.parent_id)
|
135
|
+
end
|
136
|
+
|
137
|
+
def move(target_block, position = 'after')
|
138
|
+
# ! move the block to a new location.
|
139
|
+
# ! target_block -> the targetted block to move to. : ``str``
|
140
|
+
# ! position -> where the block should be listed, in positions relative to the target_block [before, after, top-child, last-child]
|
141
|
+
positions_hash = {
|
142
|
+
'after' => 'listAfter',
|
143
|
+
'before' => 'listBefore'
|
144
|
+
}
|
145
|
+
|
146
|
+
unless positions_hash.keys.include?(position); raise ArgumentError, "Invalid position. You said: #{position}, valid options are: #{positions_hash.keys.join(', ')}"; end
|
147
|
+
|
148
|
+
position_command = positions_hash[position]
|
149
|
+
cookies = Core.options['cookies']
|
150
|
+
headers = Core.options['headers']
|
151
|
+
request_url = URLS[:UPDATE_BLOCK]
|
152
|
+
|
153
|
+
request_id = extract_id(SecureRandom.hex(16))
|
154
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
155
|
+
space_id = extract_id(SecureRandom.hex(16))
|
156
|
+
|
157
|
+
request_ids = {
|
158
|
+
request_id: request_id,
|
159
|
+
transaction_id: transaction_id,
|
160
|
+
space_id: space_id
|
161
|
+
}
|
162
|
+
|
163
|
+
check_parents = (@parent_id == target_block.parent_id)
|
164
|
+
set_block_dead_hash = Utils::BlockComponents.set_block_to_dead(@id) # kill the block this method is invoked on...
|
165
|
+
block_location_remove_hash = Utils::BlockComponents.block_location_remove(@parent_id, @id) # remove the block this method is invoked on...
|
166
|
+
parent_location_hash = Utils::BlockComponents.parent_location_add(check_parents ? @parent_id : target_block.parent_id, @id) # set parent location to alive
|
167
|
+
block_location_add_hash = Utils::BlockComponents.block_location_add(check_parents ? @parent_id : target_block.parent_id, @id, target_block.id, position_command)
|
168
|
+
last_edited_time_parent_hash = Utils::BlockComponents.last_edited_time(@parent_id)
|
169
|
+
|
170
|
+
if check_parents
|
171
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(@id)
|
172
|
+
operations = [
|
173
|
+
set_block_dead_hash,
|
174
|
+
block_location_remove_hash,
|
175
|
+
parent_location_hash,
|
176
|
+
block_location_add_hash,
|
177
|
+
last_edited_time_parent_hash,
|
178
|
+
last_edited_time_child_hash
|
179
|
+
]
|
180
|
+
else
|
181
|
+
last_edited_time_new_parent_hash = Utils::BlockComponents.last_edited_time(target_block.parent_id)
|
182
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(@id)
|
183
|
+
@parent_id = target_block.parent_id
|
184
|
+
operations = [
|
185
|
+
set_block_dead_hash,
|
186
|
+
block_location_remove_hash,
|
187
|
+
parent_location_hash,
|
188
|
+
block_location_add_hash,
|
189
|
+
last_edited_time_parent_hash,
|
190
|
+
last_edited_time_new_parent_hash,
|
191
|
+
last_edited_time_child_hash
|
192
|
+
]
|
193
|
+
end
|
194
|
+
request_body = build_payload(operations, request_ids)
|
195
|
+
response = HTTParty.post(
|
196
|
+
request_url,
|
197
|
+
body: request_body.to_json,
|
198
|
+
cookies: cookies,
|
199
|
+
headers: headers
|
200
|
+
)
|
201
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
202
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
203
|
+
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def create(block_type, block_title, target = nil, position = 'after')
|
208
|
+
# ! create a new block
|
209
|
+
# ! block_type -> the type of block to create : ``cls``
|
210
|
+
# ! block_title -> the title of the new block : ``str``
|
211
|
+
# ! target -> the block_id that the new block should be placed after. ``str``
|
212
|
+
# ! position -> 'after' or 'before'
|
213
|
+
positions_hash = {
|
214
|
+
'after' => 'listAfter',
|
215
|
+
'before' => 'listBefore'
|
216
|
+
}
|
217
|
+
unless positions_hash.keys.include?(position); raise "Invalid position. You said: #{position}, valid options are: #{positions_hash.keys.join(', ')}"; end
|
218
|
+
|
219
|
+
position_command = positions_hash[position]
|
220
|
+
|
221
|
+
cookies = Core.options['cookies']
|
222
|
+
headers = Core.options['headers']
|
223
|
+
|
224
|
+
new_block_id = extract_id(SecureRandom.hex(16))
|
225
|
+
request_id = extract_id(SecureRandom.hex(16))
|
226
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
227
|
+
space_id = extract_id(SecureRandom.hex(16))
|
228
|
+
|
229
|
+
request_ids = {
|
230
|
+
request_id: request_id,
|
231
|
+
transaction_id: transaction_id,
|
232
|
+
space_id: space_id
|
233
|
+
}
|
234
|
+
|
235
|
+
create_hash = Utils::BlockComponents.create(new_block_id, block_type.notion_type)
|
236
|
+
set_parent_alive_hash = Utils::BlockComponents.set_parent_to_alive(@id, new_block_id)
|
237
|
+
block_location_hash = Utils::BlockComponents.block_location_add(@id, @id, new_block_id, target, position_command)
|
238
|
+
last_edited_time_parent_hash = Utils::BlockComponents.last_edited_time(@id)
|
239
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(@id)
|
240
|
+
title_hash = Utils::BlockComponents.title(new_block_id, block_title)
|
241
|
+
|
242
|
+
operations = [
|
243
|
+
create_hash,
|
244
|
+
set_parent_alive_hash,
|
245
|
+
block_location_hash,
|
246
|
+
last_edited_time_parent_hash,
|
247
|
+
last_edited_time_child_hash,
|
248
|
+
title_hash
|
249
|
+
]
|
250
|
+
|
251
|
+
request_url = URLS[:UPDATE_BLOCK]
|
252
|
+
request_body = build_payload(operations, request_ids)
|
253
|
+
response = HTTParty.post(
|
254
|
+
request_url,
|
255
|
+
body: request_body.to_json,
|
256
|
+
cookies: cookies,
|
257
|
+
headers: headers
|
258
|
+
)
|
259
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
260
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
261
|
+
|
262
|
+
block_type.new(new_block_id, block_title, @id)
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def get(url_or_id)
|
268
|
+
# ! retrieve a Notion Block and return its instantiated class object.
|
269
|
+
# ! url_or_id -> the block ID or URL : ``str``
|
270
|
+
clean_id = extract_id(url_or_id)
|
271
|
+
|
272
|
+
request_body = {
|
273
|
+
pageId: clean_id,
|
274
|
+
chunkNumber: 0,
|
275
|
+
limit: 100,
|
276
|
+
verticalColumns: false
|
277
|
+
}
|
278
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
279
|
+
i = 0
|
280
|
+
while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
|
281
|
+
return {} if i >= 10
|
282
|
+
|
283
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
284
|
+
i += 1
|
285
|
+
end
|
286
|
+
block_type = extract_type(clean_id, jsonified_record_response)
|
287
|
+
block_parent_id = extract_parent_id(clean_id, jsonified_record_response)
|
288
|
+
|
289
|
+
if block_type.nil?
|
290
|
+
{}
|
291
|
+
else
|
292
|
+
block_class = NotionAPI.const_get(BLOCK_TYPES[block_type].to_s)
|
293
|
+
if block_class == NotionAPI::CollectionView
|
294
|
+
block_collection_id = extract_collection_id(clean_id, jsonified_record_response)
|
295
|
+
block_view_id = extract_view_ids(clean_id, jsonified_record_response)
|
296
|
+
collection_title = extract_collection_title(clean_id, block_collection_id, jsonified_record_response)
|
297
|
+
block_class.new(clean_id, collection_title, block_parent_id, block_collection_id, block_view_id.join)
|
298
|
+
else
|
299
|
+
block_title = extract_title(clean_id, jsonified_record_response)
|
300
|
+
block_class.new(clean_id, block_title, block_parent_id)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def update_title(new_title, request_id, transaction_id, space_id)
|
306
|
+
# ! Helper method for sending POST request to change title of block.
|
307
|
+
# ! new_title -> new title for the block : ``str``
|
308
|
+
# ! request_id -> the unique ID for the request key. Generated using SecureRandom : ``str``
|
309
|
+
# ! transaction_id -> the unique ID for the transaction key. Generated using SecureRandom: ``str``
|
310
|
+
# ! transaction_id -> the unique ID for the space key. Generated using SecureRandom: ``str``
|
311
|
+
# setup cookies, headers, and grab/create static vars for request
|
312
|
+
cookies = Core.options['cookies']
|
313
|
+
headers = Core.options['headers']
|
314
|
+
request_url = URLS[:UPDATE_BLOCK]
|
315
|
+
|
316
|
+
# set unique IDs for request
|
317
|
+
request_ids = {
|
318
|
+
request_id: request_id,
|
319
|
+
transaction_id: transaction_id,
|
320
|
+
space_id: space_id
|
321
|
+
}
|
322
|
+
|
323
|
+
# build and set operations to send to Notion
|
324
|
+
title_hash = Utils::BlockComponents.title(@id, new_title)
|
325
|
+
last_edited_time_parent_hash = Utils::BlockComponents.last_edited_time(@parent_id)
|
326
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(@id)
|
327
|
+
operations = [
|
328
|
+
title_hash,
|
329
|
+
last_edited_time_parent_hash,
|
330
|
+
last_edited_time_child_hash
|
331
|
+
]
|
332
|
+
|
333
|
+
request_body = build_payload(operations, request_ids) # defined in utils.rb
|
334
|
+
|
335
|
+
response = HTTParty.post(
|
336
|
+
request_url,
|
337
|
+
body: request_body.to_json,
|
338
|
+
cookies: cookies,
|
339
|
+
headers: headers
|
340
|
+
)
|
341
|
+
response.body
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# divider block: ---------
|
346
|
+
class DividerBlock < BlockTemplate
|
347
|
+
@notion_type = 'divider'
|
348
|
+
@type = 'divider'
|
349
|
+
|
350
|
+
def type
|
351
|
+
NotionAPI::DividerBlock.notion_type
|
352
|
+
end
|
353
|
+
|
354
|
+
class << self
|
355
|
+
attr_reader :notion_type, :type
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# To-Do block: best for checklists and tracking to-dos.
|
360
|
+
class TodoBlock < BlockTemplate
|
361
|
+
@notion_type = 'to_do'
|
362
|
+
@type = 'to_do'
|
363
|
+
|
364
|
+
def type
|
365
|
+
NotionAPI::TodoBlock.notion_type
|
366
|
+
end
|
367
|
+
|
368
|
+
class << self
|
369
|
+
attr_reader :notion_type, :type
|
370
|
+
end
|
371
|
+
|
372
|
+
def checked=(checked_value)
|
373
|
+
# ! change the checked property of the Todo Block.
|
374
|
+
# ! checked_value -> boolean value used to determine whether the block should be checked [yes] or not [no] : ``str``
|
375
|
+
# set static variables for request
|
376
|
+
cookies = Core.options['cookies']
|
377
|
+
headers = Core.options['headers']
|
378
|
+
request_url = URLS[:UPDATE_BLOCK]
|
379
|
+
|
380
|
+
# set unique values for request
|
381
|
+
request_id = extract_id(SecureRandom.hex(16))
|
382
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
383
|
+
space_id = extract_id(SecureRandom.hex(16))
|
384
|
+
request_ids = {
|
385
|
+
request_id: request_id,
|
386
|
+
transaction_id: transaction_id,
|
387
|
+
space_id: space_id
|
388
|
+
}
|
389
|
+
|
390
|
+
if %w[yes no].include?(checked_value.downcase)
|
391
|
+
checked_hash = Utils::BlockComponents.checked_todo(@id, checked_value.downcase)
|
392
|
+
last_edited_time_parent_hash = Utils::BlockComponents.last_edited_time(@parent_id)
|
393
|
+
last_edited_time_child_hash = Utils::BlockComponents.last_edited_time(@id)
|
394
|
+
|
395
|
+
operations = [
|
396
|
+
checked_hash,
|
397
|
+
last_edited_time_parent_hash,
|
398
|
+
last_edited_time_child_hash
|
399
|
+
]
|
400
|
+
request_body = build_payload(operations, request_ids)
|
401
|
+
response = HTTParty.post(
|
402
|
+
request_url,
|
403
|
+
body: request_body.to_json,
|
404
|
+
cookies: cookies,
|
405
|
+
headers: headers
|
406
|
+
)
|
407
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
408
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
409
|
+
|
410
|
+
true
|
411
|
+
else
|
412
|
+
false
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Code block: used to store code, should be assigned a coding language.
|
418
|
+
class CodeBlock < BlockTemplate
|
419
|
+
@notion_type = 'code'
|
420
|
+
@type = 'code'
|
421
|
+
|
422
|
+
def type
|
423
|
+
NotionAPI::CodeBlock.notion_type
|
424
|
+
end
|
425
|
+
|
426
|
+
class << self
|
427
|
+
attr_reader :notion_type, :type
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Header block: H1
|
432
|
+
class HeaderBlock < BlockTemplate
|
433
|
+
@notion_type = 'header'
|
434
|
+
@type = 'header'
|
435
|
+
|
436
|
+
def type
|
437
|
+
NotionAPI::HeaderBlock.notion_type
|
438
|
+
end
|
439
|
+
|
440
|
+
class << self
|
441
|
+
attr_reader :notion_type, :type
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
# SubHeader Block: H2
|
446
|
+
class SubHeaderBlock < BlockTemplate
|
447
|
+
@notion_type = 'sub_header'
|
448
|
+
@type = 'sub_header'
|
449
|
+
|
450
|
+
def type
|
451
|
+
NotionAPI::SubHeaderBlock.notion_type
|
452
|
+
end
|
453
|
+
|
454
|
+
class << self
|
455
|
+
attr_reader :notion_type, :type
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
# Sub-Sub Header Block: H3
|
460
|
+
class SubSubHeaderBlock < BlockTemplate
|
461
|
+
@notion_type = 'sub_sub_header'
|
462
|
+
@type = 'sub_sub_header'
|
463
|
+
|
464
|
+
def type
|
465
|
+
NotionAPI::SubSubHeaderBlock.notion_type
|
466
|
+
end
|
467
|
+
|
468
|
+
class << self
|
469
|
+
attr_reader :notion_type, :type
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# Page Block, entrypoint for the application
|
474
|
+
class PageBlock < BlockTemplate
|
475
|
+
@notion_type = 'page'
|
476
|
+
@type = 'page'
|
477
|
+
|
478
|
+
def type
|
479
|
+
NotionAPI::PageBlock.notion_type
|
480
|
+
end
|
481
|
+
|
482
|
+
class << self
|
483
|
+
attr_reader :notion_type, :type
|
484
|
+
end
|
485
|
+
|
486
|
+
def get_block(url_or_id)
|
487
|
+
# ! retrieve a Notion Block and return its instantiated class object.
|
488
|
+
# ! url_or_id -> the block ID or URL : ``str``
|
489
|
+
get(url_or_id)
|
490
|
+
end
|
491
|
+
|
492
|
+
def get_collection(url_or_id)
|
493
|
+
# ! retrieve a Notion Collection and return its instantiated class object.
|
494
|
+
# ! url_or_id -> the block ID or URL : ``str``
|
495
|
+
clean_id = extract_id(url_or_id)
|
496
|
+
|
497
|
+
request_body = {
|
498
|
+
pageId: clean_id,
|
499
|
+
chunkNumber: 0,
|
500
|
+
limit: 100,
|
501
|
+
verticalColumns: false
|
502
|
+
}
|
503
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
504
|
+
i = 0
|
505
|
+
while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
|
506
|
+
return {} if i >= 10
|
507
|
+
|
508
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
509
|
+
i += 1
|
510
|
+
end
|
511
|
+
block_parent_id = extract_parent_id(clean_id, jsonified_record_response)
|
512
|
+
block_collection_id = extract_collection_id(clean_id, jsonified_record_response)
|
513
|
+
block_view_id = extract_view_ids(clean_id, jsonified_record_response).join
|
514
|
+
block_title = extract_collection_title(clean_id, block_collection_id, jsonified_record_response)
|
515
|
+
|
516
|
+
CollectionView.new(clean_id, block_title, block_parent_id, block_collection_id, block_view_id)
|
517
|
+
end
|
518
|
+
|
519
|
+
def create_collection(_collection_type, collection_title, data)
|
520
|
+
# ! create a Notion Collection View and return its instantiated class object.
|
521
|
+
# ! _collection_type -> the type of collection to create : ``str``
|
522
|
+
# ! collection_title -> the title of the collection view : ``str``
|
523
|
+
# ! data -> JSON data to add to the table : ``str``
|
524
|
+
|
525
|
+
unless %w[table].include?(_collection_type) ; raise ArgumentError, "That collection type is not yet supported. Try: \"table\"."; end
|
526
|
+
cookies = Core.options['cookies']
|
527
|
+
headers = Core.options['headers']
|
528
|
+
|
529
|
+
new_block_id = extract_id(SecureRandom.hex(16))
|
530
|
+
parent_id = extract_id(SecureRandom.hex(16))
|
531
|
+
collection_id = extract_id(SecureRandom.hex(16))
|
532
|
+
view_id = extract_id(SecureRandom.hex(16))
|
533
|
+
|
534
|
+
children = []
|
535
|
+
alive_blocks = []
|
536
|
+
data.each do |_row|
|
537
|
+
child = extract_id(SecureRandom.hex(16))
|
538
|
+
children.push(child)
|
539
|
+
alive_blocks.push(Utils::CollectionViewComponents.set_collection_blocks_alive(child, collection_id))
|
540
|
+
end
|
541
|
+
|
542
|
+
request_id = extract_id(SecureRandom.hex(16))
|
543
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
544
|
+
space_id = extract_id(SecureRandom.hex(16))
|
545
|
+
|
546
|
+
request_ids = {
|
547
|
+
request_id: request_id,
|
548
|
+
transaction_id: transaction_id,
|
549
|
+
space_id: space_id
|
550
|
+
}
|
551
|
+
|
552
|
+
create_collection_view = Utils::CollectionViewComponents.create_collection_view(new_block_id, collection_id, view_id)
|
553
|
+
configure_view = Utils::CollectionViewComponents.set_view_config(new_block_id, view_id, children)
|
554
|
+
configure_columns = Utils::CollectionViewComponents.set_collection_columns(collection_id, new_block_id, data)
|
555
|
+
set_parent_alive_hash = Utils::BlockComponents.set_parent_to_alive(@id, new_block_id)
|
556
|
+
add_block_hash = Utils::BlockComponents.block_location_add(@id, @id, new_block_id, nil, 'listAfter')
|
557
|
+
new_block_edited_time = Utils::BlockComponents.last_edited_time(new_block_id)
|
558
|
+
collection_title_hash = Utils::CollectionViewComponents.set_collection_title(collection_title, collection_id)
|
559
|
+
|
560
|
+
operations = [
|
561
|
+
create_collection_view,
|
562
|
+
configure_view,
|
563
|
+
configure_columns,
|
564
|
+
set_parent_alive_hash,
|
565
|
+
add_block_hash,
|
566
|
+
new_block_edited_time,
|
567
|
+
collection_title_hash
|
568
|
+
]
|
569
|
+
operations << alive_blocks
|
570
|
+
all_ops = operations.flatten
|
571
|
+
data.each_with_index do |row, i|
|
572
|
+
child = children[i]
|
573
|
+
row.keys.each_with_index do |col_name, j|
|
574
|
+
child_component = Utils::CollectionViewComponents.insert_data(child, j.zero? ? 'title' : col_name, row[col_name])
|
575
|
+
all_ops.push(child_component)
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
request_url = URLS[:UPDATE_BLOCK]
|
580
|
+
request_body = build_payload(all_ops, request_ids)
|
581
|
+
response = HTTParty.post(
|
582
|
+
request_url,
|
583
|
+
body: request_body.to_json,
|
584
|
+
cookies: cookies,
|
585
|
+
headers: headers
|
586
|
+
)
|
587
|
+
|
588
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
589
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
590
|
+
|
591
|
+
CollectionView.new(new_block_id, collection_title, parent_id, collection_id, view_id)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
# Toggle block: best for storing children blocks
|
596
|
+
class ToggleBlock < BlockTemplate
|
597
|
+
@notion_type = 'toggle'
|
598
|
+
@type = 'toggle'
|
599
|
+
|
600
|
+
def type
|
601
|
+
NotionAPI::ToggleBlock.notion_type
|
602
|
+
end
|
603
|
+
|
604
|
+
class << self
|
605
|
+
attr_reader :notion_type, :type
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
# Bullet list block: best for an unordered list
|
610
|
+
class BulletedBlock < BlockTemplate
|
611
|
+
@notion_type = 'bulleted_list'
|
612
|
+
@type = 'bulleted_list'
|
613
|
+
|
614
|
+
def type
|
615
|
+
NotionAPI::BulletedBlock.notion_type
|
616
|
+
end
|
617
|
+
|
618
|
+
class << self
|
619
|
+
attr_reader :notion_type, :type
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
# Numbered list Block: best for an ordered list
|
624
|
+
class NumberedBlock < BlockTemplate
|
625
|
+
@notion_type = 'numbered_list'
|
626
|
+
@type = 'numbered_list'
|
627
|
+
|
628
|
+
def type
|
629
|
+
NotionAPI::NumberedBlock.notion_type
|
630
|
+
end
|
631
|
+
|
632
|
+
class << self
|
633
|
+
attr_reader :notion_type, :type
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
# best for memorable information
|
638
|
+
class QuoteBlock < BlockTemplate
|
639
|
+
@notion_type = 'quote'
|
640
|
+
@type = 'quote'
|
641
|
+
|
642
|
+
def type
|
643
|
+
NotionAPI::QuoteBlock.notion_type
|
644
|
+
end
|
645
|
+
|
646
|
+
class << self
|
647
|
+
attr_reader :notion_type, :type
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# same as quote... works similarly to page block
|
652
|
+
class CalloutBlock < BlockTemplate
|
653
|
+
@notion_type = 'callout'
|
654
|
+
@type = 'callout'
|
655
|
+
|
656
|
+
def type
|
657
|
+
NotionAPI::CalloutBlock.notion_type
|
658
|
+
end
|
659
|
+
|
660
|
+
class << self
|
661
|
+
attr_reader :notion_type, :type
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
# simiilar to code block but for mathematical functions.
|
666
|
+
class LatexBlock < BlockTemplate
|
667
|
+
@notion_type = 'equation'
|
668
|
+
@type = 'equation'
|
669
|
+
|
670
|
+
def type
|
671
|
+
NotionAPI::LatexBlock.notion_type
|
672
|
+
end
|
673
|
+
|
674
|
+
class << self
|
675
|
+
attr_reader :notion_type, :type
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
# good for just about anything (-:
|
680
|
+
class TextBlock < BlockTemplate
|
681
|
+
@notion_type = 'text'
|
682
|
+
@type = 'text'
|
683
|
+
|
684
|
+
def type
|
685
|
+
NotionAPI::TextBlock.notion_type
|
686
|
+
end
|
687
|
+
|
688
|
+
class << self
|
689
|
+
attr_reader :notion_type, :type
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
# good for visual information
|
694
|
+
class ImageBlock < BlockTemplate
|
695
|
+
@notion_type = 'image'
|
696
|
+
@type = 'image'
|
697
|
+
|
698
|
+
def type
|
699
|
+
NotionAPI::ImageBlock.notion_type
|
700
|
+
end
|
701
|
+
|
702
|
+
class << self
|
703
|
+
attr_reader :notion_type, :type
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
# maps out the headers - sub-headers - sub-sub-headers on the page
|
708
|
+
class TableOfContentsBlock < BlockTemplate
|
709
|
+
@notion_type = 'table_of_contents'
|
710
|
+
@type = 'table_of_contents'
|
711
|
+
|
712
|
+
def type
|
713
|
+
NotionAPI::TableOfContentsBlock.notion_type
|
714
|
+
end
|
715
|
+
|
716
|
+
class << self
|
717
|
+
attr_reader :notion_type, :type
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
# no use case for this yet.
|
722
|
+
class ColumnListBlock < BlockTemplate
|
723
|
+
@notion_type = 'column_list'
|
724
|
+
@type = 'column_list'
|
725
|
+
|
726
|
+
def type
|
727
|
+
NotionAPI::ColumnListBlock.notion_type
|
728
|
+
end
|
729
|
+
|
730
|
+
class << self
|
731
|
+
attr_reader :notion_type, :type
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
# no use case for this yet.
|
736
|
+
class ColumnBlock < BlockTemplate
|
737
|
+
@notion_type = 'column'
|
738
|
+
@type = 'column'
|
739
|
+
|
740
|
+
def type
|
741
|
+
NotionAPI::ColumnBlock.notion_type
|
742
|
+
end
|
743
|
+
|
744
|
+
class << self
|
745
|
+
attr_reader :notion_type, :type
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
module NotionAPI
|
751
|
+
# collection views such as tables and timelines.
|
752
|
+
class CollectionView < Core
|
753
|
+
attr_reader :id, :title, :parent_id, :collection_id, :view_id
|
754
|
+
|
755
|
+
@notion_type = 'collection_view'
|
756
|
+
@type = 'collection_view'
|
757
|
+
|
758
|
+
def type
|
759
|
+
NotionAPI::CollectionView.notion_type
|
760
|
+
end
|
761
|
+
|
762
|
+
class << self
|
763
|
+
attr_reader :notion_type, :type
|
764
|
+
end
|
765
|
+
|
766
|
+
def initialize(id, title, parent_id, collection_id, view_id)
|
767
|
+
@id = id
|
768
|
+
@title = title
|
769
|
+
@parent_id = parent_id
|
770
|
+
@collection_id = collection_id
|
771
|
+
@view_id = view_id
|
772
|
+
end
|
773
|
+
|
774
|
+
def add_row(data)
|
775
|
+
# ! add new row to Collection View table.
|
776
|
+
# ! data -> data to add to table : ``hash``
|
777
|
+
|
778
|
+
cookies = Core.options['cookies']
|
779
|
+
headers = Core.options['headers']
|
780
|
+
|
781
|
+
request_id = extract_id(SecureRandom.hex(16))
|
782
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
783
|
+
space_id = extract_id(SecureRandom.hex(16))
|
784
|
+
new_block_id = extract_id(SecureRandom.hex(16))
|
785
|
+
schema = extract_collection_schema(@collection_id, @view_id)
|
786
|
+
keys = schema.keys
|
787
|
+
col_map = {}
|
788
|
+
keys.map { |key| col_map[schema[key]['name']] = key }
|
789
|
+
|
790
|
+
request_ids = {
|
791
|
+
request_id: request_id,
|
792
|
+
transaction_id: transaction_id,
|
793
|
+
space_id: space_id
|
794
|
+
}
|
795
|
+
|
796
|
+
instantiate_row = Utils::CollectionViewComponents.add_new_row(new_block_id)
|
797
|
+
set_block_alive = Utils::CollectionViewComponents.set_collection_blocks_alive(new_block_id, @collection_id)
|
798
|
+
new_block_edited_time = Utils::BlockComponents.last_edited_time(new_block_id)
|
799
|
+
parent_edited_time = Utils::BlockComponents.last_edited_time(@parent_id)
|
800
|
+
|
801
|
+
operations = [
|
802
|
+
instantiate_row,
|
803
|
+
set_block_alive,
|
804
|
+
new_block_edited_time,
|
805
|
+
parent_edited_time
|
806
|
+
]
|
807
|
+
|
808
|
+
data.keys.each_with_index do |col_name, j|
|
809
|
+
child_component = Utils::CollectionViewComponents.insert_data(new_block_id, j.zero? ? 'title' : col_map[col_name], data[col_name])
|
810
|
+
operations.push(child_component)
|
811
|
+
end
|
812
|
+
|
813
|
+
request_url = URLS[:UPDATE_BLOCK]
|
814
|
+
request_body = build_payload(operations, request_ids)
|
815
|
+
response = HTTParty.post(
|
816
|
+
request_url,
|
817
|
+
body: request_body.to_json,
|
818
|
+
cookies: cookies,
|
819
|
+
headers: headers
|
820
|
+
)
|
821
|
+
|
822
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
823
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
824
|
+
|
825
|
+
NotionAPI::CollectionViewRow.new(new_block_id, @parent_id, @collection_id, @view_id)
|
826
|
+
end
|
827
|
+
|
828
|
+
def add_property(name, type)
|
829
|
+
# ! add a property (column) to the table.
|
830
|
+
# ! name -> name of the property : ``str``
|
831
|
+
# ! type -> type of the property : ``str``
|
832
|
+
cookies = Core.options['cookies']
|
833
|
+
headers = Core.options['headers']
|
834
|
+
|
835
|
+
request_id = extract_id(SecureRandom.hex(16))
|
836
|
+
transaction_id = extract_id(SecureRandom.hex(16))
|
837
|
+
space_id = extract_id(SecureRandom.hex(16))
|
838
|
+
|
839
|
+
request_ids = {
|
840
|
+
request_id: request_id,
|
841
|
+
transaction_id: transaction_id,
|
842
|
+
space_id: space_id
|
843
|
+
}
|
844
|
+
|
845
|
+
# create updated schema
|
846
|
+
schema = extract_collection_schema(@collection_id, @view_id)
|
847
|
+
schema[name] = {
|
848
|
+
name: name,
|
849
|
+
type: type
|
850
|
+
}
|
851
|
+
new_schema = {
|
852
|
+
schema: schema
|
853
|
+
}
|
854
|
+
|
855
|
+
add_collection_property = Utils::CollectionViewComponents.add_collection_property(@collection_id, new_schema)
|
856
|
+
|
857
|
+
operations = [
|
858
|
+
add_collection_property
|
859
|
+
]
|
860
|
+
|
861
|
+
request_url = URLS[:UPDATE_BLOCK]
|
862
|
+
request_body = build_payload(operations, request_ids)
|
863
|
+
response = HTTParty.post(
|
864
|
+
request_url,
|
865
|
+
body: request_body.to_json,
|
866
|
+
cookies: cookies,
|
867
|
+
headers: headers
|
868
|
+
)
|
869
|
+
unless response.code == 200; raise "There was an issue completing your request. Here is the response from Notion: #{response.body}, and here is the payload that was sent: #{operations}.
|
870
|
+
Please try again, and if issues persist open an issue in GitHub."; end
|
871
|
+
|
872
|
+
true
|
873
|
+
end
|
874
|
+
|
875
|
+
def row(row_id)
|
876
|
+
# ! retrieve a row from a CollectionView Table.
|
877
|
+
# ! row_id -> the ID for the row to retrieve: ``str``
|
878
|
+
clean_id = extract_id(row_id)
|
879
|
+
|
880
|
+
request_body = {
|
881
|
+
pageId: clean_id,
|
882
|
+
chunkNumber: 0,
|
883
|
+
limit: 100,
|
884
|
+
verticalColumns: false
|
885
|
+
}
|
886
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
887
|
+
schema = extract_collection_schema(@collection_id, @view_id)
|
888
|
+
keys = schema.keys
|
889
|
+
column_names = keys.map { |key| schema[key]['name'] }
|
890
|
+
i = 0
|
891
|
+
while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
|
892
|
+
return {} if i >= 10
|
893
|
+
|
894
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
895
|
+
i += 1
|
896
|
+
end
|
897
|
+
row_jsonified_response = jsonified_record_response['block'][clean_id]['value']['properties']
|
898
|
+
row_data = {}
|
899
|
+
keys.each_with_index { |key, idx| row_data[column_names[idx]] = row_jsonified_response[key] ? row_jsonified_response[key].flatten : [] }
|
900
|
+
row_data
|
901
|
+
end
|
902
|
+
|
903
|
+
def row_ids
|
904
|
+
# ! retrieve all Collection View table rows.
|
905
|
+
clean_id = extract_id(@id)
|
906
|
+
|
907
|
+
request_body = {
|
908
|
+
pageId: clean_id,
|
909
|
+
chunkNumber: 0,
|
910
|
+
limit: 100,
|
911
|
+
verticalColumns: false
|
912
|
+
}
|
913
|
+
|
914
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
915
|
+
i = 0
|
916
|
+
while jsonified_record_response.empty? || jsonified_record_response['block'].empty?
|
917
|
+
return {} if i >= 10
|
918
|
+
|
919
|
+
jsonified_record_response = get_all_block_info(clean_id, request_body)
|
920
|
+
i += 1
|
921
|
+
end
|
922
|
+
|
923
|
+
jsonified_record_response['collection_view'][@view_id]['value']['page_sort']
|
924
|
+
end
|
925
|
+
|
926
|
+
def rows
|
927
|
+
# ! returns all rows as instantiated class instances.
|
928
|
+
row_id_array = row_ids
|
929
|
+
parent_id = @parent_id
|
930
|
+
collection_id = @collection_id
|
931
|
+
view_id = @view_id
|
932
|
+
|
933
|
+
row_id_array.map { |row_id| NotionAPI::CollectionViewRow.new(row_id, parent_id, collection_id, view_id) }
|
934
|
+
end
|
935
|
+
|
936
|
+
private
|
937
|
+
|
938
|
+
def extract_collection_schema(collection_id, view_id)
|
939
|
+
# ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
|
940
|
+
# ! collection_id -> the collection ID : ``str``
|
941
|
+
# ! view_id -> the view ID : ``str``
|
942
|
+
cookies = Core.options['cookies']
|
943
|
+
headers = Core.options['headers']
|
944
|
+
|
945
|
+
query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
|
946
|
+
|
947
|
+
request_url = URLS[:GET_COLLECTION]
|
948
|
+
response = HTTParty.post(
|
949
|
+
request_url,
|
950
|
+
body: query_collection_hash.to_json,
|
951
|
+
cookies: cookies,
|
952
|
+
headers: headers
|
953
|
+
)
|
954
|
+
response['recordMap']['collection'][collection_id]['value']['schema']
|
955
|
+
end
|
956
|
+
end
|
957
|
+
# Class for each row in a Collection View Table.
|
958
|
+
class CollectionViewRow < Core
|
959
|
+
@notion_type = 'table_row'
|
960
|
+
@type = 'table_row'
|
961
|
+
|
962
|
+
def type
|
963
|
+
NotionAPI::CollectionViewRow.notion_type
|
964
|
+
end
|
965
|
+
|
966
|
+
class << self
|
967
|
+
attr_reader :notion_type, :type, :parent_id
|
968
|
+
end
|
969
|
+
|
970
|
+
attr_reader :parent_id, :id
|
971
|
+
def initialize(id, parent_id, collection_id, view_id)
|
972
|
+
@id = id
|
973
|
+
@parent_id = parent_id
|
974
|
+
@collection_id = collection_id
|
975
|
+
@view_id = view_id
|
976
|
+
end
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
# gather a list of all the classes defined here...
|
981
|
+
Classes = NotionAPI.constants.select { |c| NotionAPI.const_get(c).is_a? Class and c.to_s != 'BlockTemplate' and c.to_s != 'Core' and c.to_s !='Client' }
|
982
|
+
notion_types = []
|
983
|
+
Classes.each { |cls| notion_types.push(NotionAPI.const_get(cls).notion_type) }
|
984
|
+
BLOCK_TYPES = notion_types.zip(Classes).to_h
|