notion 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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.
@@ -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
+ ```
@@ -0,0 +1,5 @@
1
+ require "notion_api/client"
2
+ require "notion_api/core"
3
+ require "notion_api/blocks"
4
+ require "notion_api/utils"
5
+ require "notion_api/markdown"
@@ -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