notion-ruby-client 0.0.1 → 0.0.6

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -0
  3. data/.rspec +2 -0
  4. data/.rspec_status +18 -0
  5. data/.rubocop.yml +46 -0
  6. data/.rubocop_todo.yml +86 -0
  7. data/CHANGELOG.md +26 -0
  8. data/CODE_OF_CONDUCT.md +128 -0
  9. data/Gemfile.lock +95 -0
  10. data/README.md +192 -3
  11. data/Rakefile +18 -0
  12. data/lib/notion-ruby-client.rb +6 -8
  13. data/lib/notion/api/endpoints.rb +6 -0
  14. data/lib/notion/api/endpoints/blocks.rb +53 -0
  15. data/lib/notion/api/endpoints/databases.rb +78 -0
  16. data/lib/notion/api/endpoints/pages.rb +69 -0
  17. data/lib/notion/api/endpoints/users.rb +7 -3
  18. data/lib/notion/api/errors.rb +7 -1
  19. data/lib/notion/api/errors/notion_error.rb +2 -2
  20. data/lib/notion/config.rb +3 -1
  21. data/lib/notion/faraday/request.rb +8 -2
  22. data/lib/notion/faraday/response/raise_error.rb +3 -4
  23. data/lib/notion/pagination/cursor.rb +2 -2
  24. data/lib/notion/version.rb +3 -2
  25. data/notion-ruby-client-0.0.4.gem +0 -0
  26. data/spec/fixtures/notion/block_append_children.yml +135 -0
  27. data/spec/fixtures/notion/block_children.yml +137 -0
  28. data/spec/fixtures/notion/create_page.yml +137 -0
  29. data/spec/fixtures/notion/database.yml +133 -0
  30. data/spec/fixtures/notion/database_query.yml +135 -0
  31. data/spec/fixtures/notion/databases_list.yml +133 -0
  32. data/spec/fixtures/notion/page.yml +133 -0
  33. data/spec/fixtures/notion/paginated_block_children.yml +521 -0
  34. data/spec/fixtures/notion/paginated_database_query.yml +135 -0
  35. data/spec/fixtures/notion/paginated_databases_list.yml +133 -0
  36. data/spec/fixtures/notion/paginated_users_list.yml +133 -0
  37. data/spec/fixtures/notion/update_page.yml +136 -0
  38. data/spec/fixtures/notion/users.yml +132 -0
  39. data/spec/fixtures/notion/users_list.yml +133 -0
  40. data/spec/notion/api/endpoints/blocks_spec.rb +40 -0
  41. data/spec/notion/api/endpoints/databases_spec.rb +40 -0
  42. data/spec/notion/api/endpoints/pages_spec.rb +64 -0
  43. data/spec/notion/api/endpoints/users_spec.rb +26 -0
  44. data/spec/notion/config_spec.rb +16 -0
  45. data/spec/notion/version_spec.rb +8 -0
  46. data/spec/spec_helper.rb +22 -0
  47. data/spec/support/token.rb +10 -0
  48. data/spec/support/vcr.rb +16 -0
  49. metadata +67 -8
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ require 'bundler/gem_tasks'
5
+
6
+ Bundler.setup :default, :development
7
+
8
+ require 'rspec/core'
9
+ require 'rspec/core/rake_task'
10
+
11
+ RSpec::Core::RakeTask.new(:spec) do |spec|
12
+ spec.pattern = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ require 'rubocop/rake_task'
16
+ RuboCop::RakeTask.new
17
+
18
+ task default: %i[spec rubocop]
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'hashie'
7
+
2
8
  require_relative 'notion/version'
3
9
  require_relative 'notion/logger'
4
10
  require_relative 'notion/config'
5
11
 
6
12
  # Messages
7
- require 'hashie'
8
13
  require_relative 'notion/messages/message'
9
- require_relative 'notion/messages/formatting'
10
-
11
- # Web API
12
- require 'faraday'
13
- require 'faraday_middleware'
14
- require 'json'
15
- require 'logger'
16
14
 
17
15
  require_relative 'notion/config'
18
16
  require_relative 'notion/api/errors/notion_error'
@@ -1,10 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'endpoints/blocks'
4
+ require_relative 'endpoints/databases'
5
+ require_relative 'endpoints/pages'
3
6
  require_relative 'endpoints/users'
4
7
 
5
8
  module Notion
6
9
  module Api
7
10
  module Endpoints
11
+ include Blocks
12
+ include Databases
13
+ include Pages
8
14
  include Users
9
15
  end
10
16
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notion
4
+ module Api
5
+ module Endpoints
6
+ module Blocks
7
+ #
8
+ # Returns a paginated array of Block objects contained in the
9
+ # block of the requested path using the ID specified.
10
+ #
11
+ # Returns a 404 HTTP response if any of the following are true:
12
+ # - the ID does not exist
13
+ # - the bot doesn't have access to the block with the given ID
14
+ #
15
+ # Returns a 400 or 429 HTTP response if the request exceeds Notion's Request limits.
16
+ #
17
+ # @option options [id] :id
18
+ # Block to get children info on.
19
+ def block_children(options = {})
20
+ throw ArgumentError.new('Required arguments :id missing') if options[:id].nil?
21
+ if block_given?
22
+ Pagination::Cursor.new(self, :block_children, options).each do |page|
23
+ yield page
24
+ end
25
+ else
26
+ get("blocks/#{options[:id]}/children", options)
27
+ end
28
+ end
29
+
30
+ #
31
+ # Creates and appends new children blocks to the parent block
32
+ # in the requested path using the ID specified. Returns the Block
33
+ # object being appended to.
34
+ #
35
+ # Returns a 404 HTTP response if any of the following are true:
36
+ # - the ID does not exist
37
+ # - the bot doesn't have access to the block with the given ID
38
+ #
39
+ # Returns a 400 or 429 HTTP response if the request exceeds Notion's Request limits.
40
+ #
41
+ # @option options [id] :id
42
+ # Block to get children info on.
43
+ #
44
+ # @option options [[Object]] :children
45
+ # Children blocks to append
46
+ def block_append_children(options = {})
47
+ throw ArgumentError.new('Required arguments :id missing') if options[:id].nil?
48
+ patch("blocks/#{options[:id]}/children", options)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notion
4
+ module Api
5
+ module Endpoints
6
+ module Databases
7
+ #
8
+ # Retrieves a Database object using the ID specified in the request.
9
+ #
10
+ # Returns a 404 HTTP response if the database doesn't exist, or if the bot
11
+ # doesn't have access to the database. Returns a 429 HTTP response if the
12
+ # request exceeds Notion's Request limits.
13
+ #
14
+ # @option options [id] :id
15
+ # Database to get info on.
16
+ def database(options = {})
17
+ throw ArgumentError.new('Required arguments :id missing') if options[:id].nil?
18
+ get("databases/#{options[:id]}")
19
+ end
20
+
21
+ #
22
+ # Gets a paginated array of Page object s contained in the requested database,
23
+ # filtered and ordered according to the filter and sort objects provided in the request.
24
+ #
25
+ # Filters are similar to the filters provided in the Notion UI. Filters operate
26
+ # on database properties and can be combined. If no filter is provided, all the
27
+ # pages in the database will be returned with pagination.
28
+ #
29
+ # Sorts are similar to the sorts provided in the Notion UI. Sorts operate on
30
+ # database properties and can be combined. The order of the sorts in the request
31
+ # matter, with earlier sorts taking precedence over later ones.
32
+ #
33
+ # @option options [id] :id
34
+ # Database to query.
35
+ #
36
+ # @option options [Object] :filter
37
+ # When supplied, limits which pages are returned based on the provided criteria.
38
+ #
39
+ # @option options [[Object]] :sorts
40
+ # When supplied, sorts the results based on the provided criteria.
41
+ #
42
+ # @option options [UUID] :start_cursor
43
+ # Paginate through collections of data by setting the cursor parameter
44
+ # to a start_cursor attribute returned by a previous request's next_cursor.
45
+ # Default value fetches the first "page" of the collection.
46
+ # See pagination for more detail.
47
+ def database_query(options = {})
48
+ throw ArgumentError.new('Required arguments :id missing') if options[:id].nil?
49
+ if block_given?
50
+ Pagination::Cursor.new(self, :database_query, options).each do |page|
51
+ yield page
52
+ end
53
+ else
54
+ post("databases/#{options[:id]}/query", options)
55
+ end
56
+ end
57
+
58
+ #
59
+ # Returns a paginated list of Databases objects for the workspace.
60
+ #
61
+ # @option options [UUID] :start_cursor
62
+ # Paginate through collections of data by setting the cursor parameter
63
+ # to a start_cursor attribute returned by a previous request's next_cursor.
64
+ # Default value fetches the first "page" of the collection.
65
+ # See pagination for more detail.
66
+ def databases_list(options = {})
67
+ if block_given?
68
+ Pagination::Cursor.new(self, :databases_list, options).each do |page|
69
+ yield page
70
+ end
71
+ else
72
+ get('databases', options)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notion
4
+ module Api
5
+ module Endpoints
6
+ module Pages
7
+ #
8
+ # Retrieves a 📄Page object using the ID specified in the request path.
9
+ # Note that this version of the API only exposes page properties, not page content
10
+ #
11
+ # @option options [id] :id
12
+ # Page to get info on.
13
+ #
14
+ # @option options [bool] :archived
15
+ # Set to true to retrieve an archived page; must be false or omitted to
16
+ # retrieve a page that has not been archived. Defaults to false.
17
+ def page(options = {})
18
+ throw ArgumentError.new('Required arguments :id missing') if options[:id].nil?
19
+ get("pages/#{options[:id]}?archived=#{options[:archived]}")
20
+ end
21
+
22
+ #
23
+ # Creates a new page in the specified database.
24
+ # Later iterations of the API will support creating pages outside databases.
25
+ # Note that this iteration of the API will only expose page properties, not
26
+ # page content, as described in the data model.
27
+ #
28
+ # @option options [Object] :parent
29
+ # Parent of the page, which is always going to be a database in this version of the API.
30
+ #
31
+ # @option options [Object] :properties
32
+ # Properties of this page.
33
+ # The schema for the page's keys and values is described by the properties of
34
+ # the database this page belongs to. key string Name of a property as it
35
+ # appears in Notion, or property ID. value object Object containing a value
36
+ # specific to the property type, e.g. {"checkbox": true}.
37
+ #
38
+ # @option options [Object] :children
39
+ # An optional array of Block objects representing the Page’s conent
40
+ def create_page(options = {})
41
+ throw ArgumentError.new('Required arguments :parent.database_id missing') if options.dig(:parent, :database_id).nil?
42
+ post("pages", options)
43
+ end
44
+
45
+ #
46
+ # Updates a page by setting the values of any properties specified in the
47
+ # JSON body of the request. Properties that are not set via parameters will
48
+ # remain unchanged.
49
+ #
50
+ # Note that this iteration of the API will only expose page properties, not page
51
+ # content, as described in the data model.
52
+ #
53
+ # @option options [id] :id
54
+ # Page to get info on.
55
+ #
56
+ # @option options [Object] :properties
57
+ # Properties of this page.
58
+ # The schema for the page's keys and values is described by the properties of
59
+ # the database this page belongs to. key string Name of a property as it
60
+ # appears in Notion, or property ID. value object Object containing a value
61
+ # specific to the property type, e.g. {"checkbox": true}.
62
+ def update_page(options = {})
63
+ throw ArgumentError.new('Required arguments :id missing') if options[:id].nil?
64
+ patch("pages/#{options[:id]}", options)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -16,15 +16,19 @@ module Notion
16
16
 
17
17
  #
18
18
  # Returns a paginated list of User objects for the workspace.
19
- # @option options [Object] :start_cursor
20
- # Paginate through collections of data by setting the cursor parameter to a start_cursor attribute returned by a previous request's next_cursor. Default value fetches the first "page" of the collection. See pagination for more detail.
19
+ #
20
+ # @option options [UUID] :start_cursor
21
+ # Paginate through collections of data by setting the cursor parameter
22
+ # to a start_cursor attribute returned by a previous request's next_cursor.
23
+ # Default value fetches the first "page" of the collection.
24
+ # See pagination for more detail.
21
25
  def users_list(options = {})
22
26
  if block_given?
23
27
  Pagination::Cursor.new(self, :users_list, options).each do |page|
24
28
  yield page
25
29
  end
26
30
  else
27
- get("users?start_cursor=#{options[:start_cursor]}")
31
+ get("users", options)
28
32
  end
29
33
  end
30
34
  end
@@ -3,16 +3,22 @@
3
3
  module Notion
4
4
  module Api
5
5
  module Errors
6
+ class BadRequest < NotionError; end
7
+ class Forbidden < NotionError; end
6
8
  class InternalError < NotionError; end
7
9
  class InvalidRequest < NotionError; end
8
10
  class ObjectNotFound < NotionError; end
9
11
  class TooManyRequests < NotionError; end
12
+ class Unauthorized < NotionError; end
10
13
 
11
14
  ERROR_CLASSES = {
15
+ 'bad_request' => BadRequest,
16
+ 'forbidden' => Forbidden,
12
17
  'internal_error' => InternalError,
13
18
  'invalid_request' => InvalidRequest,
14
19
  'object_not_found' => ObjectNotFound,
15
- 'rate_limited' => TooManyRequests
20
+ 'rate_limited' => TooManyRequests,
21
+ 'unauthorized' => Unauthorized
16
22
  }.freeze
17
23
  end
18
24
  end
@@ -5,8 +5,8 @@ module Notion
5
5
  class NotionError < ::Faraday::Error
6
6
  attr_reader :response
7
7
 
8
- def initialize(message, response = nil)
9
- super message
8
+ def initialize(message, details, response = nil)
9
+ super("#{message} #{details}")
10
10
  @response = response
11
11
  end
12
12
  end
data/lib/notion/config.rb CHANGED
@@ -47,4 +47,6 @@ module Notion
47
47
  Config
48
48
  end
49
49
  end
50
- end
50
+ end
51
+
52
+ Notion::Config.reset
@@ -6,6 +6,10 @@ module Notion
6
6
  request(:get, path, options)
7
7
  end
8
8
 
9
+ def patch(path, options = {})
10
+ request(:patch, path, options)
11
+ end
12
+
9
13
  def post(path, options = {})
10
14
  request(:post, path, options)
11
15
  end
@@ -23,12 +27,14 @@ module Notion
23
27
  def request(method, path, options)
24
28
  response = connection.send(method) do |request|
25
29
  request.headers['Authorization'] = "Bearer #{token}"
30
+ request.headers['Notion-Version'] = Notion::NOTION_REQUEST_VERSION
26
31
  case method
27
32
  when :get, :delete
28
33
  request.url(path, options)
29
- when :post, :put
34
+ when :post, :put, :patch
35
+ request.headers['Content-Type'] = 'application/json'
30
36
  request.path = path
31
- request.body = options unless options.empty?
37
+ request.body = options.to_json unless options.empty?
32
38
  end
33
39
  request.options.merge!(options.delete(:request)) if options.key?(:request)
34
40
  end
@@ -6,19 +6,18 @@ module Notion
6
6
  def on_complete(env)
7
7
  raise Notion::Api::Errors::TooManyRequests, env.response if env.status == 429
8
8
 
9
- return unless env.success?
9
+ return if env.success?
10
10
 
11
11
  body = env.body
12
12
  return unless body
13
- return if env.status == 200
14
13
 
15
14
  error_code = body['code']
16
15
  error_message = body['message']
17
- error_message = body['details']
16
+ error_details = body['details']
18
17
 
19
18
  error_class = Notion::Api::Errors::ERROR_CLASSES[error_code]
20
19
  error_class ||= Notion::Api::Errors::NotionError
21
- raise error_class.new(error_message, env.response)
20
+ raise error_class.new(error_message, error_details, env.response)
22
21
  end
23
22
 
24
23
  def call(env)
@@ -23,10 +23,10 @@ module Notion
23
23
  next_cursor = nil
24
24
  retry_count = 0
25
25
  loop do
26
- query = { limit: client.default_page_size }.merge(params).merge(start_cursor: next_cursor)
26
+ query = next_cursor.nil? ? params : params.merge(start_cursor: next_cursor)
27
27
  begin
28
28
  response = client.send(verb, query)
29
- rescue Notion::Api::Errors::TooManyRequestsError => e
29
+ rescue Notion::Api::Errors::TooManyRequests => e
30
30
  raise e if retry_count >= max_retries
31
31
 
32
32
  client.logger.debug("#{self.class}##{__method__}") { e.to_s }
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Notion
3
- VERSION = '0.0.1'
4
- end
3
+ VERSION = '0.0.6'
4
+ NOTION_REQUEST_VERSION = '2021-05-11'
5
+ end
Binary file
@@ -0,0 +1,135 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: patch
5
+ uri: https://api.notion.com/v1/blocks/723578f1-6e51-450c-8ee1-09158c19fd4c/children
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"id":"723578f1-6e51-450c-8ee1-09158c19fd4c","children":[{"object":"block","type":"heading_2","heading_2":{"text":[{"type":"text","text":{"content":"Another
9
+ Heading"}}]}}]}'
10
+ headers:
11
+ Accept:
12
+ - application/json; charset=utf-8
13
+ User-Agent:
14
+ - Notion Ruby Client/0.0.4
15
+ Authorization:
16
+ - Bearer <NOTION_API_TOKEN>
17
+ Content-Type:
18
+ - application/json
19
+ Accept-Encoding:
20
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
21
+ response:
22
+ status:
23
+ code: 200
24
+ message: OK
25
+ headers:
26
+ Date:
27
+ - Sun, 25 Apr 2021 21:05:49 GMT
28
+ Content-Type:
29
+ - application/json; charset=utf-8
30
+ Transfer-Encoding:
31
+ - chunked
32
+ Connection:
33
+ - keep-alive
34
+ Set-Cookie:
35
+ - __cfduid=d34e3fa5c4766df6ac5561da522624b761619384749; expires=Tue, 25-May-21
36
+ 21:05:49 GMT; path=/; domain=.notion.com; HttpOnly; SameSite=Lax
37
+ - notion_browser_id=e006545d-4f97-4497-8d9c-491386cb6d2a; Domain=www.notion.so;
38
+ Path=/; Expires=Wed, 01 Jan 2053 22:52:29 GMT; Secure
39
+ X-Dns-Prefetch-Control:
40
+ - 'off'
41
+ X-Frame-Options:
42
+ - SAMEORIGIN
43
+ Strict-Transport-Security:
44
+ - max-age=5184000; includeSubDomains
45
+ X-Download-Options:
46
+ - noopen
47
+ X-Content-Type-Options:
48
+ - nosniff
49
+ X-Xss-Protection:
50
+ - 1; mode=block
51
+ Referrer-Policy:
52
+ - same-origin
53
+ Content-Security-Policy:
54
+ - 'script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' https://gist.github.com
55
+ https://apis.google.com https://api.amplitude.com https://widget.intercom.io
56
+ https://js.intercomcdn.com https://logs-01.loggly.com https://cdn.segment.com
57
+ https://analytics.pgncs.notion.so https://checkout.stripe.com https://js.stripe.com/v3
58
+ https://embed.typeform.com https://admin.typeform.com https://platform.twitter.com
59
+ https://cdn.syndication.twimg.com https://www.googletagmanager.com https://x.clearbitjs.com;
60
+ connect-src ''self'' https://msgstore.www.notion.so wss://msgstore.www.notion.so
61
+ ws://localhost:* https://notion-emojis.s3-us-west-2.amazonaws.com https://s3-us-west-2.amazonaws.com
62
+ https://s3.us-west-2.amazonaws.com https://notion-production-snapshots-2.s3.us-west-2.amazonaws.com
63
+ https: http: https://api.amplitude.com https://api.embed.ly https://js.intercomcdn.com
64
+ https://api-iam.intercom.io wss://nexus-websocket-a.intercom.io https://logs-01.loggly.com
65
+ https://api.segment.io https://api.pgncs.notion.so https://checkout.stripe.com
66
+ https://js.stripe.com/v3 https://cdn.contentful.com https://preview.contentful.com
67
+ https://images.ctfassets.net https://api.unsplash.com https://boards-api.greenhouse.io;
68
+ font-src ''self'' data: https://cdnjs.cloudflare.com https://js.intercomcdn.com;
69
+ img-src ''self'' data: blob: https: https://platform.twitter.com https://syndication.twitter.com
70
+ https://pbs.twimg.com https://ton.twimg.com www.googletagmanager.com; style-src
71
+ ''self'' ''unsafe-inline'' https://cdnjs.cloudflare.com https://github.githubassets.com
72
+ https://platform.twitter.com https://ton.twimg.com; frame-src https: http:;
73
+ media-src https: http:'
74
+ X-Content-Security-Policy:
75
+ - 'script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' https://gist.github.com
76
+ https://apis.google.com https://api.amplitude.com https://widget.intercom.io
77
+ https://js.intercomcdn.com https://logs-01.loggly.com https://cdn.segment.com
78
+ https://analytics.pgncs.notion.so https://checkout.stripe.com https://js.stripe.com/v3
79
+ https://embed.typeform.com https://admin.typeform.com https://platform.twitter.com
80
+ https://cdn.syndication.twimg.com https://www.googletagmanager.com https://x.clearbitjs.com;
81
+ connect-src ''self'' https://msgstore.www.notion.so wss://msgstore.www.notion.so
82
+ ws://localhost:* https://notion-emojis.s3-us-west-2.amazonaws.com https://s3-us-west-2.amazonaws.com
83
+ https://s3.us-west-2.amazonaws.com https://notion-production-snapshots-2.s3.us-west-2.amazonaws.com
84
+ https: http: https://api.amplitude.com https://api.embed.ly https://js.intercomcdn.com
85
+ https://api-iam.intercom.io wss://nexus-websocket-a.intercom.io https://logs-01.loggly.com
86
+ https://api.segment.io https://api.pgncs.notion.so https://checkout.stripe.com
87
+ https://js.stripe.com/v3 https://cdn.contentful.com https://preview.contentful.com
88
+ https://images.ctfassets.net https://api.unsplash.com https://boards-api.greenhouse.io;
89
+ font-src ''self'' data: https://cdnjs.cloudflare.com https://js.intercomcdn.com;
90
+ img-src ''self'' data: blob: https: https://platform.twitter.com https://syndication.twitter.com
91
+ https://pbs.twimg.com https://ton.twimg.com www.googletagmanager.com; style-src
92
+ ''self'' ''unsafe-inline'' https://cdnjs.cloudflare.com https://github.githubassets.com
93
+ https://platform.twitter.com https://ton.twimg.com; frame-src https: http:;
94
+ media-src https: http:'
95
+ X-Webkit-Csp:
96
+ - 'script-src ''self'' ''unsafe-inline'' ''unsafe-eval'' https://gist.github.com
97
+ https://apis.google.com https://api.amplitude.com https://widget.intercom.io
98
+ https://js.intercomcdn.com https://logs-01.loggly.com https://cdn.segment.com
99
+ https://analytics.pgncs.notion.so https://checkout.stripe.com https://js.stripe.com/v3
100
+ https://embed.typeform.com https://admin.typeform.com https://platform.twitter.com
101
+ https://cdn.syndication.twimg.com https://www.googletagmanager.com https://x.clearbitjs.com;
102
+ connect-src ''self'' https://msgstore.www.notion.so wss://msgstore.www.notion.so
103
+ ws://localhost:* https://notion-emojis.s3-us-west-2.amazonaws.com https://s3-us-west-2.amazonaws.com
104
+ https://s3.us-west-2.amazonaws.com https://notion-production-snapshots-2.s3.us-west-2.amazonaws.com
105
+ https: http: https://api.amplitude.com https://api.embed.ly https://js.intercomcdn.com
106
+ https://api-iam.intercom.io wss://nexus-websocket-a.intercom.io https://logs-01.loggly.com
107
+ https://api.segment.io https://api.pgncs.notion.so https://checkout.stripe.com
108
+ https://js.stripe.com/v3 https://cdn.contentful.com https://preview.contentful.com
109
+ https://images.ctfassets.net https://api.unsplash.com https://boards-api.greenhouse.io;
110
+ font-src ''self'' data: https://cdnjs.cloudflare.com https://js.intercomcdn.com;
111
+ img-src ''self'' data: blob: https: https://platform.twitter.com https://syndication.twitter.com
112
+ https://pbs.twimg.com https://ton.twimg.com www.googletagmanager.com; style-src
113
+ ''self'' ''unsafe-inline'' https://cdnjs.cloudflare.com https://github.githubassets.com
114
+ https://platform.twitter.com https://ton.twimg.com; frame-src https: http:;
115
+ media-src https: http:'
116
+ Etag:
117
+ - W/"e5-DFeqv/gYemlg3XfmjAU4nnWmtn8"
118
+ Vary:
119
+ - Accept-Encoding
120
+ Cf-Cache-Status:
121
+ - DYNAMIC
122
+ Cf-Request-Id:
123
+ - '09ac73642e00000818408a7000000001'
124
+ Expect-Ct:
125
+ - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
126
+ Server:
127
+ - cloudflare
128
+ Cf-Ray:
129
+ - 645a8819e9030818-CDG
130
+ body:
131
+ encoding: UTF-8
132
+ string: '{"object":"block","id":"723578f1-6e51-450c-8ee1-09158c19fd4c","created_time":"2021-01-29T20:50:51.917Z","last_edited_time":"2021-04-25T21:05:49.706Z","has_children":true,"type":"child_page","child_page":{"title":"A
133
+ Notion page"}}'
134
+ recorded_at: Sun, 25 Apr 2021 21:05:49 GMT
135
+ recorded_with: VCR 6.0.0