notion 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://travis-ci.com/danmurphy1217/notion-ruby) [](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
|