notion 1.0.0 → 1.0.5

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.
@@ -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