notion 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'core'
4
+ require_relative 'blocks'
4
5
 
5
6
  module NotionAPI
6
7
  # acts as the 'main interface' to the methods of this package.
@@ -42,13 +42,27 @@ module NotionAPI
42
42
  end
43
43
 
44
44
  block_id = clean_id
45
- block_title = extract_title(clean_id, jsonified_record_response)
46
45
  block_type = extract_type(clean_id, jsonified_record_response)
47
46
  block_parent_id = extract_parent_id(clean_id, jsonified_record_response)
48
47
 
49
- raise 'the URL or ID passed to the get_page method must be that of a Page Block.' if block_type != 'page'
50
-
51
- PageBlock.new(block_id, block_title, block_parent_id)
48
+ raise 'the URL or ID passed to the get_page method must be that of a Page Block.' if !['collection_view_page', 'page'].include?(block_type)
49
+
50
+ if block_type == "page"
51
+ block_title = extract_title(clean_id, jsonified_record_response)
52
+ PageBlock.new(block_id, block_title, block_parent_id)
53
+ elsif block_type == "collection_view_page"
54
+ collection_id = extract_collection_id(block_id, jsonified_record_response)
55
+ block_title = extract_collection_title(clean_id, collection_id, jsonified_record_response)
56
+ view_id = extract_view_ids(block_id, jsonified_record_response)[0]
57
+ schema = extract_collection_schema(collection_id, view_id, jsonified_record_response)
58
+ column_mappings = schema.keys
59
+ column_names = column_mappings.map { |mapping| schema[mapping]['name']}
60
+
61
+ collection_view_page = CollectionViewPage.new(block_id, block_title, block_parent_id, collection_id, view_id)
62
+ collection_view_page.instance_variable_set(:@column_names, column_names)
63
+ CollectionView.class_eval{attr_reader :column_names}
64
+ collection_view_page
65
+ end
52
66
  end
53
67
 
54
68
  def children(url_or_id = @id)
@@ -113,11 +127,34 @@ module NotionAPI
113
127
  children_ids(url_or_id).empty? ? [] : children_ids(url_or_id)[-1]
114
128
  end
115
129
 
130
+ def get_block_props_and_format(clean_id, block_title)
131
+ request_body = {
132
+ pageId: clean_id,
133
+ chunkNumber: 0,
134
+ limit: 100,
135
+ verticalColumns: false
136
+ }
137
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
138
+ i = 0
139
+ while jsonified_record_response.empty?
140
+ return {:properties => {title: [[block_title]]}, :format => {}} if i >= 10
141
+
142
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
143
+ i += 1
144
+ end
145
+ properties = jsonified_record_response['block'][clean_id]['value']['properties']
146
+ formats = jsonified_record_response['block'][clean_id]['value']['format']
147
+ return {
148
+ :properties => properties,
149
+ :format => formats
150
+ }
151
+ end
152
+
116
153
  def get_all_block_info(_clean_id, body)
117
154
  # ! retrieves all info pertaining to a block Id.
118
155
  # ! clean_id -> the block ID or URL cleaned : ``str``
119
156
  Core.options['cookies'][:token_v2] = @@token_v2
120
- Core.options['headers']['x-notion-active-user-header'] = @active_user_header
157
+ Core.options['headers']['x-notion-active-user-header'] = @@active_user_header
121
158
  cookies = Core.options['cookies']
122
159
  headers = Core.options['headers']
123
160
 
@@ -150,7 +187,7 @@ module NotionAPI
150
187
  # titles for images are called source, while titles for text-based blocks are called title, so lets dynamically grab it
151
188
  # https://stackoverflow.com/questions/23765996/get-all-keys-from-ruby-hash/23766007
152
189
  title_value = filter_nil_blocks[clean_id]['value']['properties'].keys[0]
153
- Core.type_whitelist.include?(filter_nil_blocks[clean_id]['value']['type']) ? nil : jsonified_record_response['block'][clean_id]['value']['properties'][title_value].flatten[0]
190
+ Core.type_whitelist.include?(filter_nil_blocks[clean_id]['value']['type']) ? nil : jsonified_record_response['block'][clean_id]['value']['properties'][title_value].flatten[0]
154
191
 
155
192
  end
156
193
  end
@@ -160,7 +197,7 @@ module NotionAPI
160
197
  # ! clean_id -> the cleaned block ID: ``str``
161
198
  # ! collection_id -> the collection ID: ``str``
162
199
  # ! jsonified_record_response -> parsed JSON representation of a notion response object : ``Json``
163
- jsonified_record_response['collection'][collection_id]['value']['name'].flatten.join if jsonified_record_response['collection']
200
+ jsonified_record_response['collection'][collection_id]['value']['name'].flatten.join if jsonified_record_response['collection'] and jsonified_record_response['collection'][collection_id]['value']['name']
164
201
  end
165
202
 
166
203
  def extract_type(clean_id, jsonified_record_response)
@@ -193,7 +230,7 @@ module NotionAPI
193
230
  def extract_view_ids(clean_id, jsonified_record_response)
194
231
  jsonified_record_response['block'][clean_id]['value']['view_ids'] || []
195
232
  end
196
-
233
+
197
234
  def extract_id(url_or_id)
198
235
  # ! parse and clean the URL or ID object provided.
199
236
  # ! url_or_id -> the block ID or URL : ``str``
@@ -213,5 +250,47 @@ module NotionAPI
213
250
  raise ArgumentError, 'Expected a Notion page URL or a page ID. Please consult the documentation for further information.'
214
251
  end
215
252
  end
253
+
254
+ def extract_collection_schema(collection_id, view_id, response = {})
255
+ # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
256
+ # ! collection_id -> the collection ID : ``str``
257
+ # ! view_id -> the view ID : ``str``
258
+ cookies = Core.options['cookies']
259
+ headers = Core.options['headers']
260
+
261
+ if response.empty?
262
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
263
+
264
+ request_url = URLS[:GET_COLLECTION]
265
+ response = HTTParty.post(
266
+ request_url,
267
+ body: query_collection_hash.to_json,
268
+ cookies: cookies,
269
+ headers: headers
270
+ )
271
+ response['recordMap']['collection'][collection_id]['value']['schema']
272
+ else
273
+ response['collection'][collection_id]['value']['schema']
274
+ end
275
+ end
276
+
277
+ def extract_collection_data(collection_id, view_id)
278
+ # ! retrieve the collection scehma. Useful for 'building' the backbone for a table.
279
+ # ! collection_id -> the collection ID : ``str``
280
+ # ! view_id -> the view ID : ``str``
281
+ cookies = Core.options['cookies']
282
+ headers = Core.options['headers']
283
+
284
+ query_collection_hash = Utils::CollectionViewComponents.query_collection(collection_id, view_id, '')
285
+
286
+ request_url = URLS[:GET_COLLECTION]
287
+ response = HTTParty.post(
288
+ request_url,
289
+ body: query_collection_hash.to_json,
290
+ cookies: cookies,
291
+ headers: headers
292
+ )
293
+ response['recordMap']
294
+ end
216
295
  end
217
296
  end
@@ -0,0 +1,17 @@
1
+ require_relative "template"
2
+
3
+ module NotionAPI
4
+ # Bullet list block: best for an unordered list
5
+ class BulletedBlock < BlockTemplate
6
+ @notion_type = "bulleted_list"
7
+ @type = "bulleted_list"
8
+
9
+ def type
10
+ NotionAPI::BulletedBlock.notion_type
11
+ end
12
+
13
+ class << self
14
+ attr_reader :notion_type, :type
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module NotionAPI
2
+
3
+ # same as quote... works similarly to page block
4
+ class CalloutBlock < BlockTemplate
5
+ @notion_type = 'callout'
6
+ @type = 'callout'
7
+
8
+ def type
9
+ NotionAPI::CalloutBlock.notion_type
10
+ end
11
+
12
+ class << self
13
+ attr_reader :notion_type, :type
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module NotionAPI
2
+
3
+ # Code block: used to store code, should be assigned a coding language.
4
+ class CodeBlock < BlockTemplate
5
+ @notion_type = 'code'
6
+ @type = 'code'
7
+
8
+ def type
9
+ NotionAPI::CodeBlock.notion_type
10
+ end
11
+
12
+ class << self
13
+ attr_reader :notion_type, :type
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,315 @@
1
+ module NotionAPI
2
+ # collection views such as tables and timelines.
3
+ class CollectionView < Core
4
+ attr_reader :id, :title, :parent_id, :collection_id, :view_id
5
+
6
+ @notion_type = "collection_view"
7
+ @type = "collection_view"
8
+
9
+ def type
10
+ NotionAPI::CollectionView.notion_type
11
+ end
12
+
13
+ class << self
14
+ attr_reader :notion_type, :type
15
+ end
16
+
17
+ def initialize(id, title, parent_id, collection_id, view_id)
18
+ @id = id
19
+ @title = title
20
+ @parent_id = parent_id
21
+ @collection_id = collection_id
22
+ @view_id = view_id
23
+ end
24
+
25
+ def add_row(data)
26
+ # ! add new row to Collection View table.
27
+ # ! data -> data to add to table : ``hash``
28
+
29
+ cookies = Core.options["cookies"]
30
+ headers = Core.options["headers"]
31
+
32
+ request_id = extract_id(SecureRandom.hex(16))
33
+ transaction_id = extract_id(SecureRandom.hex(16))
34
+ space_id = extract_id(SecureRandom.hex(16))
35
+ new_block_id = extract_id(SecureRandom.hex(16))
36
+ schema = extract_collection_schema(@collection_id, @view_id)
37
+ keys = schema.keys
38
+ col_map = {}
39
+ keys.map { |key| col_map[schema[key]["name"]] = key }
40
+
41
+ request_ids = {
42
+ request_id: request_id,
43
+ transaction_id: transaction_id,
44
+ space_id: space_id,
45
+ }
46
+
47
+ instantiate_row = Utils::CollectionViewComponents.add_new_row(new_block_id)
48
+ set_block_alive = Utils::CollectionViewComponents.set_collection_blocks_alive(new_block_id, @collection_id)
49
+ new_block_edited_time = Utils::BlockComponents.last_edited_time(new_block_id)
50
+ parent_edited_time = Utils::BlockComponents.last_edited_time(@parent_id)
51
+
52
+ operations = [
53
+ instantiate_row,
54
+ set_block_alive,
55
+ new_block_edited_time,
56
+ parent_edited_time,
57
+ ]
58
+
59
+ data.keys.each_with_index do |col_name, j|
60
+ child_component = Utils::CollectionViewComponents.insert_data(new_block_id, j.zero? ? "title" : col_map[col_name], data[col_name], j.zero? ? schema["title"]["type"] : schema[col_map[col_name]]["type"])
61
+ operations.push(child_component)
62
+ end
63
+
64
+ request_url = URLS[:UPDATE_BLOCK]
65
+ request_body = build_payload(operations, request_ids)
66
+ response = HTTParty.post(
67
+ request_url,
68
+ body: request_body.to_json,
69
+ cookies: cookies,
70
+ headers: headers,
71
+ )
72
+
73
+ 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}.
74
+ Please try again, and if issues persist open an issue in GitHub."; end
75
+
76
+ NotionAPI::CollectionViewRow.new(new_block_id, @parent_id, @collection_id, @view_id)
77
+ end
78
+
79
+ def add_property(name, type)
80
+ # ! add a property (column) to the table.
81
+ # ! name -> name of the property : ``str``
82
+ # ! type -> type of the property : ``str``
83
+ cookies = Core.options["cookies"]
84
+ headers = Core.options["headers"]
85
+
86
+ request_id = extract_id(SecureRandom.hex(16))
87
+ transaction_id = extract_id(SecureRandom.hex(16))
88
+ space_id = extract_id(SecureRandom.hex(16))
89
+
90
+ request_ids = {
91
+ request_id: request_id,
92
+ transaction_id: transaction_id,
93
+ space_id: space_id,
94
+ }
95
+
96
+ # create updated schema
97
+ schema = extract_collection_schema(@collection_id, @view_id)
98
+ schema[name] = {
99
+ name: name,
100
+ type: type,
101
+ }
102
+ new_schema = {
103
+ schema: schema,
104
+ }
105
+
106
+ add_collection_property = Utils::CollectionViewComponents.add_collection_property(@collection_id, new_schema)
107
+
108
+ operations = [
109
+ add_collection_property,
110
+ ]
111
+
112
+ request_url = URLS[:UPDATE_BLOCK]
113
+ request_body = build_payload(operations, request_ids)
114
+ response = HTTParty.post(
115
+ request_url,
116
+ body: request_body.to_json,
117
+ cookies: cookies,
118
+ headers: headers,
119
+ )
120
+ 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}.
121
+ Please try again, and if issues persist open an issue in GitHub."; end
122
+
123
+ true
124
+ end
125
+
126
+ def row(row_id)
127
+ # ! retrieve a row from a CollectionView Table.
128
+ # ! row_id -> the ID for the row to retrieve: ``str``
129
+ clean_id = extract_id(row_id)
130
+
131
+ request_body = {
132
+ pageId: clean_id,
133
+ chunkNumber: 0,
134
+ limit: 100,
135
+ verticalColumns: false,
136
+ }
137
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
138
+
139
+ i = 0
140
+ while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
141
+ return {} if i >= 10
142
+
143
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
144
+ i += 1
145
+ end
146
+
147
+ collection_data = extract_collection_data(@collection_id, @view_id)
148
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
149
+ column_mappings = schema.keys
150
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
151
+
152
+ collection_row = CollectionViewRow.new(row_id, @parent_id, @collection_id, @view_id)
153
+ collection_row.instance_variable_set(:@column_names, column_names)
154
+ CollectionViewRow.class_eval { attr_reader :column_names }
155
+
156
+ row_data = collection_data["block"][collection_row.id]
157
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
158
+
159
+ collection_row
160
+ end
161
+
162
+ def row_ids
163
+ # ! retrieve all Collection View table rows.
164
+ clean_id = extract_id(@id)
165
+
166
+ request_body = {
167
+ pageId: clean_id,
168
+ chunkNumber: 0,
169
+ limit: 100,
170
+ verticalColumns: false,
171
+ }
172
+
173
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
174
+ i = 0
175
+ while jsonified_record_response.empty? || jsonified_record_response["block"].empty?
176
+ return {} if i >= 10
177
+
178
+ jsonified_record_response = get_all_block_info(clean_id, request_body)
179
+ i += 1
180
+ end
181
+
182
+ jsonified_record_response["collection_view"][@view_id]["value"]["page_sort"]
183
+ end
184
+
185
+ def rows
186
+ # ! returns all rows as instantiated class instances.
187
+ row_id_array = row_ids
188
+ parent_id = @parent_id
189
+ collection_id = @collection_id
190
+ view_id = @view_id
191
+ collection_data = extract_collection_data(@collection_id, @view_id)
192
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
193
+ column_mappings = schema.keys
194
+ column_names = column_mappings.map { |mapping| schema[mapping]["name"] }
195
+
196
+ row_instances = row_id_array.map { |row_id| NotionAPI::CollectionViewRow.new(row_id, parent_id, collection_id, view_id) }
197
+ clean_row_instances = row_instances.filter { |row| collection_data["block"][row.id] }
198
+ clean_row_instances.each { |row| row.instance_variable_set(:@column_names, column_names) }
199
+ CollectionViewRow.class_eval { attr_reader :column_names }
200
+
201
+ clean_row_instances.each do |collection_row|
202
+ row_data = collection_data["block"][collection_row.id]
203
+ create_singleton_methods_and_instance_variables(collection_row, row_data)
204
+ end
205
+ clean_row_instances
206
+ end
207
+ def create_singleton_methods_and_instance_variables(row, row_data)
208
+ # ! creates singleton methods for each property in a CollectionView.
209
+ # ! row -> the block ID of the 'row' to retrieve: ``str``
210
+ # ! row_data -> the data corresponding to that row, should be key-value pairs where the keys are the columns: ``hash``
211
+ collection_data = extract_collection_data(@collection_id, @view_id)
212
+ schema = collection_data["collection"][collection_id]["value"]["schema"]
213
+ column_mappings = schema.keys
214
+ column_hash = {}
215
+ column_names = column_mappings.map { |mapping| column_hash[mapping] = schema[mapping]["name"].downcase }
216
+
217
+ column_hash.keys.each_with_index do |column, i|
218
+ # loop over the column names...
219
+ # set instance variables for each column, allowing the dev to 'read' the column value
220
+ cleaned_column = column_hash[column].split(" ").join("_").downcase.to_sym
221
+
222
+ # p row_data["value"]["properties"][column_mappings[i]], !(row_data["value"]["properties"][column] or row_data["value"]["properties"][column_mappings[i]])
223
+ if row_data["value"]["properties"].nil? or row_data["value"]["properties"][column].nil?
224
+ value = ""
225
+ else
226
+ value = row_data["value"]["properties"][column][0][0]
227
+ end
228
+
229
+ row.instance_variable_set("@#{cleaned_column}", value)
230
+ CollectionViewRow.class_eval { attr_reader cleaned_column }
231
+ # then, define singleton methods for each column that are used to update the table cell
232
+ row.define_singleton_method("#{cleaned_column}=") do |new_value|
233
+ # neat way to get the name of the currently invoked method...
234
+ parsed_method = __method__.to_s[0...-1].split("_").join(" ")
235
+ cookies = Core.options["cookies"]
236
+ headers = Core.options["headers"]
237
+
238
+ request_id = extract_id(SecureRandom.hex(16))
239
+ transaction_id = extract_id(SecureRandom.hex(16))
240
+ space_id = extract_id(SecureRandom.hex(16))
241
+
242
+ request_ids = {
243
+ request_id: request_id,
244
+ transaction_id: transaction_id,
245
+ space_id: space_id,
246
+ }
247
+
248
+ update_property_value = Utils::CollectionViewComponents.update_property_value(@id, column_hash.key(parsed_method), new_value)
249
+
250
+ operations = [
251
+ update_property_value,
252
+ ]
253
+
254
+ request_url = URLS[:UPDATE_BLOCK]
255
+ request_body = build_payload(operations, request_ids)
256
+ response = HTTParty.post(
257
+ request_url,
258
+ body: request_body.to_json,
259
+ cookies: cookies,
260
+ headers: headers,
261
+ )
262
+ 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}.
263
+ Please try again, and if issues persist open an issue in GitHub.";end
264
+
265
+ # set the instance variable to the updated value!
266
+ _ = row.instance_variable_set("@#{__method__.to_s[0...-1]}", new_value)
267
+ row
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ # class that represents each row in a CollectionView
274
+ class CollectionViewRow < Core
275
+ @notion_type = "table_row"
276
+ @type = "table_row"
277
+
278
+ def type
279
+ NotionAPI::CollectionViewRow.notion_type
280
+ end
281
+
282
+ def inspect
283
+ "CollectionViewRow - id: #{self.id} - parent id: #{self.parent_id}"
284
+ end
285
+
286
+ class << self
287
+ attr_reader :notion_type, :type, :parent_id
288
+ end
289
+
290
+ attr_reader :parent_id, :id
291
+
292
+ def initialize(id, parent_id, collection_id, view_id)
293
+ @id = id
294
+ @parent_id = parent_id
295
+ @collection_id = collection_id
296
+ @view_id = view_id
297
+ end
298
+ end
299
+
300
+ # class that represents a CollectionViewPage, inheriting all properties from CollectionView
301
+ class CollectionViewPage < CollectionView
302
+ @notion_type = "collection_view_page"
303
+ @type = "collection_view_page"
304
+
305
+ def type
306
+ NotionAPI::CollectionViewRow.notion_type
307
+ end
308
+
309
+ class << self
310
+ attr_reader :notion_type, :type, :parent_id
311
+ end
312
+
313
+ attr_reader :parent_id, :id
314
+ end
315
+ end