wikidatum 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,368 +3,436 @@
3
3
  require 'faraday'
4
4
  require 'faraday/net_http'
5
5
 
6
- class Wikidatum::Client
7
- ITEM_REGEX = /^Q?\d+$/.freeze
8
- STATEMENT_REGEX = /^Q?\d+\$[\w-]+$/.freeze
9
-
10
- # @return [String] the root URL of the Wikibase instance we want to interact
11
- # with. If not provided, will default to Wikidata.
12
- attr_reader :wikibase_url
13
-
14
- # @return [Boolean] whether this client instance should identify itself
15
- # as a bot when making requests.
16
- attr_reader :bot
17
-
18
- # @return [String] the UserAgent header to send with all requests to the
19
- # Wikibase API.
20
- attr_reader :user_agent
21
-
22
- # Create a new Wikidatum::Client to interact with the Wikibase REST API.
23
- #
24
- # @example
25
- # wikidatum_client = Wikidatum::Client.new(
26
- # user_agent: 'REPLACE ME WITH THE NAME OF YOUR BOT!',
27
- # wikibase_url: 'https://www.wikidata.org',
28
- # bot: true
29
- # )
30
- #
31
- # @param user_agent [String] The UserAgent header to send with all requests
32
- # to the Wikibase API.
33
- # @param wikibase_url [String] The root URL of the Wikibase instance we want
34
- # to interact with. If not provided, will default to
35
- # `https://www.wikidata.org`. Do not include a `/` at the end of the URL.
36
- # @param bot [Boolean] Whether requests sent by this client instance should
37
- # be registered as bot requests. Defaults to `true`.
38
- # @return [Wikidatum::Client]
39
- def initialize(user_agent:, wikibase_url: 'https://www.wikidata.org', bot: true)
40
- raise ArgumentError, "Wikibase URL must not end with a `/`, got #{wikibase_url.inspect}." if wikibase_url.end_with?('/')
41
-
42
- # TODO: Add the Ruby gem version to the UserAgent automatically, and
43
- # restrict the ability for end-users to actually set the UserAgent?
44
- @user_agent = user_agent
45
- @wikibase_url = wikibase_url
46
- @bot = bot
47
-
48
- Faraday.default_adapter = :net_http
49
- end
6
+ module Wikidatum
7
+ class Client
8
+ ITEM_REGEX = /^Q?\d+$/.freeze
9
+ PROPERTY_REGEX = /^P?\d+$/.freeze
10
+ STATEMENT_REGEX = /^Q?\d+\$[\w-]+$/.freeze
11
+ VALID_RANKS = ['preferred', 'normal', 'deprecated'].freeze
12
+ VALID_DATA_TYPES = [
13
+ 'Wikidatum::DataType::GlobeCoordinate',
14
+ 'Wikidatum::DataType::MonolingualText',
15
+ 'Wikidatum::DataType::NoValue',
16
+ 'Wikidatum::DataType::Quantity',
17
+ 'Wikidatum::DataType::SomeValue',
18
+ 'Wikidatum::DataType::Time',
19
+ 'Wikidatum::DataType::WikibaseItem',
20
+ 'Wikidatum::DataType::WikibaseString'
21
+ ].freeze
22
+
23
+ # @return [String] the root URL of the Wikibase instance we want to interact
24
+ # with. If not provided, will default to Wikidata.
25
+ attr_reader :wikibase_url
26
+
27
+ # @return [Boolean] whether this client instance should identify itself
28
+ # as a bot when making requests.
29
+ attr_reader :bot
30
+
31
+ # @return [String] the UserAgent header to send with all requests to the
32
+ # Wikibase API.
33
+ attr_reader :user_agent
34
+
35
+ # @return [Boolean] whether this client should allow non-GET requests if
36
+ # authentication hasn't been provided. Defaults to false.
37
+ attr_reader :allow_ip_edits
38
+
39
+ # Create a new Wikidatum::Client to interact with the Wikibase REST API.
40
+ #
41
+ # @example
42
+ # wikidatum_client = Wikidatum::Client.new(
43
+ # user_agent: 'Bot Name',
44
+ # wikibase_url: 'https://www.wikidata.org',
45
+ # bot: true
46
+ # )
47
+ #
48
+ # @param user_agent [String] The UserAgent header to send with all requests
49
+ # to the Wikibase API. This will be prepended with the string "Wikidatum
50
+ # Ruby gem vX.X.X:".
51
+ # @param wikibase_url [String] The root URL of the Wikibase instance we want
52
+ # to interact with. If not provided, will default to
53
+ # `https://www.wikidata.org`. Do not include a `/` at the end of the URL.
54
+ # @param bot [Boolean] Whether requests sent by this client instance should
55
+ # be registered as bot requests. Defaults to `true`.
56
+ # @param allow_ip_edits [Boolean] whether this client should allow non-GET
57
+ # requests if authentication hasn't been provided. Defaults to false. If
58
+ # this is set to true, the IP address of the device from which the
59
+ # request was sent will be credited for the edit. Make sure not to allow
60
+ # these edits if you don't want your IP address (and in many cases, a
61
+ # very close approximation of your physical location) exposed publicly.
62
+ # @return [Wikidatum::Client]
63
+ def initialize(user_agent:, wikibase_url: 'https://www.wikidata.org', bot: true, allow_ip_edits: false)
64
+ raise ArgumentError, "Wikibase URL must not end with a `/`, got #{wikibase_url.inspect}." if wikibase_url.end_with?('/')
65
+
66
+ @user_agent = "Wikidatum Ruby gem v#{Wikidatum::VERSION}: #{user_agent}"
67
+ @wikibase_url = wikibase_url
68
+ @bot = bot
69
+ @allow_ip_edits = allow_ip_edits
70
+
71
+ Faraday.default_adapter = :net_http
72
+ end
50
73
 
51
- # Get an item from the Wikibase API based on its QID.
52
- #
53
- # @example
54
- # wikidatum_client.item(id: 'Q123')
55
- # wikidatum_client.item(id: 123)
56
- # wikidatum_client.item(id: '123')
57
- #
58
- # @param id [String, Integer] Either a string or integer representation of
59
- # the item's QID, e.g. `"Q123"`, `"123"`, or `123`.
60
- # @return [Wikidatum::Item]
61
- def item(id:)
62
- raise ArgumentError, "#{id.inspect} is an invalid Wikibase QID. Must be an integer, a string representation of an integer, or in the format 'Q123'." unless id.is_a?(Integer) || id.match?(ITEM_REGEX)
74
+ # Get an item from the Wikibase API based on its QID.
75
+ #
76
+ # @example
77
+ # wikidatum_client.item(id: 'Q123')
78
+ # wikidatum_client.item(id: 123)
79
+ # wikidatum_client.item(id: '123')
80
+ #
81
+ # @param id [String, Integer] Either a string or integer representation of
82
+ # the item's QID, e.g. `"Q123"`, `"123"`, or `123`.
83
+ # @return [Wikidatum::Item]
84
+ def item(id:)
85
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase QID. Must be an integer, a string representation of an integer, or in the format 'Q123'." unless id.is_a?(Integer) || id.match?(ITEM_REGEX)
63
86
 
64
- id = coerce_item_id(id)
87
+ id = coerce_item_id(id)
65
88
 
66
- response = get_request("/entities/items/#{id}")
89
+ response = get_request("/entities/items/#{id}")
67
90
 
68
- puts JSON.pretty_generate(response) if ENV['DEBUG']
91
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
69
92
 
70
- Wikidatum::Item.marshal_load(response)
71
- end
93
+ Wikidatum::Item.marshal_load(response)
94
+ end
72
95
 
73
- # Get a statement from the Wikibase API based on its ID.
74
- #
75
- # @example
76
- # wikidatum_client.statement(id: 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac')
77
- #
78
- # @param id [String] A string representation of the statement's ID.
79
- # @return [Wikidatum::Statement]
80
- def statement(id:)
81
- raise ArgumentError, "#{id.inspect} is an invalid Wikibase Statement ID. Must be a string in the format 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac'." unless id.match?(STATEMENT_REGEX)
96
+ # Get a statement from the Wikibase API based on its ID.
97
+ #
98
+ # @example
99
+ # wikidatum_client.statement(id: 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac')
100
+ #
101
+ # @param id [String] A string representation of the statement's ID.
102
+ # @return [Wikidatum::Statement]
103
+ def statement(id:)
104
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase Statement ID. Must be a string in the format 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac'." unless id.match?(STATEMENT_REGEX)
82
105
 
83
- response = get_request("/statements/#{id}")
106
+ response = get_request("/statements/#{id}")
84
107
 
85
- puts JSON.pretty_generate(response) if ENV['DEBUG']
108
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
86
109
 
87
- Wikidatum::Statement.marshal_load(response)
88
- end
110
+ Wikidatum::Statement.marshal_load(response)
111
+ end
89
112
 
90
- # Add a statement to an item.
91
- #
92
- # NOTE: Adding references/qualifiers with `add_statement` is untested and
93
- # effectively unsupported for now.
94
- #
95
- # @example Add a string statement.
96
- # wikidatum_client.add_statement(
97
- # id: 'Q123',
98
- # property: 'P23',
99
- # datavalue: Wikidatum::DataValueType::WikibaseString.new(string: 'Foo'),
100
- # comment: 'Adding something or another.'
101
- # )
102
- #
103
- # @example Add a 'no value' statement.
104
- # wikidatum_client.add_statement(
105
- # id: 'Q123',
106
- # property: 'P124',
107
- # datavalue: Wikidatum::DataValueType::NoValue.new(
108
- # type: :no_value,
109
- # value: nil
110
- # )
111
- # )
112
- #
113
- # @example Add an 'unknown value' statement.
114
- # wikidatum_client.add_statement(
115
- # id: 'Q123',
116
- # property: 'P124',
117
- # datavalue: Wikidatum::DataValueType::SomeValue.new(
118
- # type: :some_value,
119
- # value: nil
120
- # )
121
- # )
122
- #
123
- # @example Add a globe coordinate statement.
124
- # wikidatum_client.add_statement(
125
- # id: 'Q123',
126
- # property: 'P124',
127
- # datavalue: Wikidatum::DataValueType::GlobeCoordinate.new(
128
- # latitude: 52.51666,
129
- # longitude: 13.3833,
130
- # precision: 0.01666,
131
- # globe: 'https://wikidata.org/entity/Q2'
132
- # )
133
- # )
134
- #
135
- # @example Add a monolingual text statement.
136
- # wikidatum_client.add_statement(
137
- # id: 'Q123',
138
- # property: 'P124',
139
- # datavalue: Wikidatum::DataValueType::MonolingualText.new(
140
- # language: 'en',
141
- # text: 'Foobar'
142
- # )
143
- # )
144
- #
145
- # @example Add a quantity statement.
146
- # wikidatum_client.add_statement(
147
- # id: 'Q123',
148
- # property: 'P124',
149
- # datavalue: Wikidatum::DataValueType::Quantity.new(
150
- # amount: '+12',
151
- # upper_bound: nil,
152
- # lower_bound: nil,
153
- # unit: 'https://wikidata.org/entity/Q1234'
154
- # )
155
- # )
156
- #
157
- # @example Add a time statement.
158
- # wikidatum_client.add_statement(
159
- # id: 'Q123',
160
- # property: 'P124',
161
- # datavalue: Wikidatum::DataValueType::Time.new(
162
- # time: '+2022-08-12T00:00:00Z',
163
- # time_zone: 0,
164
- # precision: 11,
165
- # calendar_model: 'https://wikidata.org/entity/Q1234'
166
- # )
167
- # )
168
- #
169
- # @example Add a Wikibase item statement.
170
- # wikidatum_client.add_statement(
171
- # id: 'Q123',
172
- # property: 'P124',
173
- # datavalue: Wikidatum::DataValueType::WikibaseEntityId.new(
174
- # entity_type: 'item',
175
- # numeric_id: 1234,
176
- # id: 'Q1234'
177
- # )
178
- # )
179
- #
180
- # @param id [String] the ID of the item on which the statement will be added.
181
- # @param property [String] property ID in the format 'P123'.
182
- # @param datavalue [Wikidatum::DataValueType::GlobeCoordinate, Wikidatum::DataValueType::MonolingualText, Wikidatum::DataValueType::Quantity, Wikidatum::DataValueType::WikibaseString, Wikidatum::DataValueType::Time, Wikidatum::DataValueType::WikibaseEntityId, Wikidatum::DataValueType::NoValue, Wikidatum::DataValueType::SomeValue] the datavalue of the statement being created.
183
- # @param datatype [String, nil] if nil, it'll determine the type based on what was passed for the statement argument. This may differ from the type of the Statement's datavalue (for example with the 'url' type).
184
- # @param qualifiers [Hash<String, Array<Wikidatum::Snak>>]
185
- # @param references [Array<Wikidatum::Reference>]
186
- # @param rank [String]
187
- # @param tags [Array<String>]
188
- # @param comment [String, nil]
189
- # @return [Boolean] True if the request succeeded.
190
- def add_statement(id:, property:, datavalue:, datatype: nil, qualifiers: {}, references: [], rank: 'normal', tags: [], comment: nil)
191
- raise ArgumentError, "#{id.inspect} is an invalid Wikibase QID. Must be an integer, a string representation of an integer, or in the format 'Q123'." unless id.is_a?(Integer) || id.match?(ITEM_REGEX)
192
-
193
- id = coerce_item_id(id)
194
-
195
- # Unless datatype is set explicitly by the caller, just assume we can pull the
196
- # default from the datavalue class.
197
- datatype ||= datavalue.wikibase_datatype
198
-
199
- case datavalue.class.to_s
200
- when 'Wikidatum::DataValueType::NoValue'
201
- statement_hash = {
202
- mainsnak: {
203
- snaktype: 'novalue',
204
- property: property,
205
- datatype: datatype
113
+ # Add a statement to an item.
114
+ #
115
+ # NOTE: Adding references/qualifiers with `add_statement` is untested and
116
+ # effectively unsupported for now.
117
+ #
118
+ # @example Add a string statement.
119
+ # wikidatum_client.add_statement(
120
+ # id: 'Q123',
121
+ # property: 'P23',
122
+ # value: Wikidatum::DataType::WikibaseString.new(string: 'Foo'),
123
+ # comment: 'Adding something or another.'
124
+ # )
125
+ #
126
+ # @example Add a 'no value' statement.
127
+ # wikidatum_client.add_statement(
128
+ # id: 'Q123',
129
+ # property: 'P124',
130
+ # value: Wikidatum::DataType::NoValue.new(
131
+ # type: :no_value,
132
+ # value: nil
133
+ # )
134
+ # )
135
+ #
136
+ # @example Add an 'unknown value' statement.
137
+ # wikidatum_client.add_statement(
138
+ # id: 'Q123',
139
+ # property: 'P124',
140
+ # value: Wikidatum::DataType::SomeValue.new(
141
+ # type: :some_value,
142
+ # value: nil
143
+ # )
144
+ # )
145
+ #
146
+ # @example Add a globe coordinate statement.
147
+ # wikidatum_client.add_statement(
148
+ # id: 'Q123',
149
+ # property: 'P124',
150
+ # value: Wikidatum::DataType::GlobeCoordinate.new(
151
+ # latitude: 52.51666,
152
+ # longitude: 13.3833,
153
+ # precision: 0.01666,
154
+ # globe: 'https://wikidata.org/entity/Q2'
155
+ # )
156
+ # )
157
+ #
158
+ # @example Add a monolingual text statement.
159
+ # wikidatum_client.add_statement(
160
+ # id: 'Q123',
161
+ # property: 'P124',
162
+ # value: Wikidatum::DataType::MonolingualText.new(
163
+ # language: 'en',
164
+ # text: 'Foobar'
165
+ # )
166
+ # )
167
+ #
168
+ # @example Add a quantity statement.
169
+ # wikidatum_client.add_statement(
170
+ # id: 'Q123',
171
+ # property: 'P124',
172
+ # value: Wikidatum::DataType::Quantity.new(
173
+ # amount: '+12',
174
+ # unit: 'https://wikidata.org/entity/Q1234'
175
+ # )
176
+ # )
177
+ #
178
+ # @example Add a time statement.
179
+ # wikidatum_client.add_statement(
180
+ # id: 'Q123',
181
+ # property: 'P124',
182
+ # value: Wikidatum::DataType::Time.new(
183
+ # time: '+2022-08-12T00:00:00Z',
184
+ # precision: 11,
185
+ # calendar_model: 'https://wikidata.org/entity/Q1234'
186
+ # )
187
+ # )
188
+ #
189
+ # @example Add a Wikibase item statement.
190
+ # wikidatum_client.add_statement(
191
+ # id: 'Q123',
192
+ # property: 'P124',
193
+ # value: Wikidatum::DataType::WikibaseItem.new(
194
+ # id: 'Q1234'
195
+ # )
196
+ # )
197
+ #
198
+ # @param id [String, Integer] the ID of the item on which the statement will be added.
199
+ # @param property [String, Integer] property ID in the format 'P123', or an integer.
200
+ # @param value [Wikidatum::DataType::GlobeCoordinate, Wikidatum::DataType::MonolingualText, Wikidatum::DataType::Quantity, Wikidatum::DataType::WikibaseString, Wikidatum::DataType::Time, Wikidatum::DataType::WikibaseItem, Wikidatum::DataType::NoValue, Wikidatum::DataType::SomeValue] the value of the statement being created.
201
+ # @param qualifiers [Array<Wikidatum::Qualifier>]
202
+ # @param references [Array<Wikidatum::Reference>]
203
+ # @param rank [String, Symbol] Valid ranks are 'preferred', 'normal', or
204
+ # 'deprecated'. Defaults to 'normal'. Also accepts Symbol for these ranks.
205
+ # @param tags [Array<String>]
206
+ # @param comment [String, nil]
207
+ # @return [Boolean] True if the request succeeded.
208
+ def add_statement(id:, property:, value:, qualifiers: [], references: [], rank: 'normal', tags: [], comment: nil)
209
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase QID. Must be an integer, a string representation of an integer, or in the format 'Q123'." unless id.is_a?(Integer) || id.match?(ITEM_REGEX)
210
+ raise ArgumentError, "#{property.inspect} is an invalid Wikibase PID. Must be an integer, a string representation of an integer, or in the format 'P123'." unless property.is_a?(Integer) || property.match?(PROPERTY_REGEX)
211
+ raise ArgumentError, "#{rank.inspect} is an invalid rank. Must be normal, preferred, or deprecated." unless VALID_RANKS.include?(rank.to_s)
212
+ raise ArgumentError, "Expected an instance of one of Wikidatum::DataType's subclasses for value, but got #{value.inspect}." unless VALID_DATA_TYPES.include?(value.class.to_s)
213
+
214
+ id = coerce_item_id(id)
215
+ property = coerce_property_id(property)
216
+
217
+ case value.class.to_s
218
+ when 'Wikidatum::DataType::NoValue'
219
+ statement_hash = {
220
+ property: {
221
+ id: property
222
+ },
223
+ value: {
224
+ type: 'novalue'
225
+ }
206
226
  }
207
- }
208
- when 'Wikidatum::DataValueType::SomeValue'
209
- statement_hash = {
210
- mainsnak: {
211
- snaktype: 'somevalue',
212
- property: property,
213
- datatype: datatype
227
+ when 'Wikidatum::DataType::SomeValue'
228
+ statement_hash = {
229
+ property: {
230
+ id: property
231
+ },
232
+ value: {
233
+ type: 'somevalue'
234
+ }
214
235
  }
215
- }
216
- when 'Wikidatum::DataValueType::GlobeCoordinate', 'Wikidatum::DataValueType::MonolingualText', 'Wikidatum::DataValueType::Quantity', 'Wikidatum::DataValueType::WikibaseString', 'Wikidatum::DataValueType::Time', 'Wikidatum::DataValueType::WikibaseEntityId'
217
- statement_hash = {
218
- mainsnak: {
219
- snaktype: 'value',
220
- property: property,
221
- datatype: datatype,
222
- datavalue: {
223
- type: datavalue.wikibase_type,
224
- value: datavalue.marshal_dump
236
+ when 'Wikidatum::DataType::GlobeCoordinate', 'Wikidatum::DataType::MonolingualText', 'Wikidatum::DataType::Quantity', 'Wikidatum::DataType::WikibaseString', 'Wikidatum::DataType::Time', 'Wikidatum::DataType::WikibaseItem'
237
+ statement_hash = {
238
+ property: {
239
+ id: property
240
+ },
241
+ value: {
242
+ type: 'value',
243
+ content: value.marshal_dump
225
244
  }
226
245
  }
227
- }
228
- else
229
- raise ArgumentError, "Expected an instance of one of Wikidatum::DataValueType's subclasses for datavalue, but got #{datavalue.inspect}."
230
- end
246
+ end
231
247
 
232
- body = { statement: statement_hash.merge({ qualifiers: qualifiers, references: references, rank: rank, type: "statement" }) }
248
+ body = { statement: statement_hash.merge({ qualifiers: qualifiers, references: references, rank: rank.to_s, type: 'statement' }) }
233
249
 
234
- response = post_request("/entities/items/#{id}/statements", body, tags: tags, comment: comment)
250
+ response = post_request("/entities/items/#{id}/statements", body, tags: tags, comment: comment)
235
251
 
236
- puts JSON.pretty_generate(response) if ENV['DEBUG']
252
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
237
253
 
238
- response.success?
239
- end
254
+ response.success?
255
+ end
240
256
 
241
- # Delete a statement from an item.
242
- #
243
- # @example
244
- # wikidatum_client.delete_statement(
245
- # id: 'Q123$4543523c-1d1d-1111-1e1e-11b11111b1f1',
246
- # comment: "Deleting this statement because it's bad."
247
- # )
248
- #
249
- # @param id [String] the ID of the statemnt being deleted.
250
- # @param tags [Array<String>]
251
- # @param comment [String, nil]
252
- # @return [Boolean] True if the request succeeded.
253
- def delete_statement(id:, tags: [], comment: nil)
254
- raise ArgumentError, "#{id.inspect} is an invalid Wikibase Statement ID. Must be a string in the format 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac'." unless id.match?(STATEMENT_REGEX)
255
-
256
- response = delete_request("/statements/#{id}", tags: tags, comment: comment)
257
-
258
- puts JSON.pretty_generate(response) if ENV['DEBUG']
259
-
260
- response.success?
261
- end
257
+ # Delete a statement from an item.
258
+ #
259
+ # @example
260
+ # wikidatum_client.delete_statement(
261
+ # id: 'Q123$4543523c-1d1d-1111-1e1e-11b11111b1f1',
262
+ # comment: "Deleting this statement because it's bad."
263
+ # )
264
+ #
265
+ # @param id [String] the ID of the statemnt being deleted.
266
+ # @param tags [Array<String>]
267
+ # @param comment [String, nil]
268
+ # @return [Boolean] True if the request succeeded.
269
+ def delete_statement(id:, tags: [], comment: nil)
270
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase Statement ID. Must be a string in the format 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac'." unless id.match?(STATEMENT_REGEX)
271
+
272
+ response = delete_request("/statements/#{id}", tags: tags, comment: comment)
273
+
274
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
275
+
276
+ response.success?
277
+ end
262
278
 
263
- private
279
+ # Is the current instance of Client authenticated as a Wikibase user?
280
+ #
281
+ # @return [Boolean]
282
+ def authenticated?
283
+ # TODO: Make it possible for this to be true once authentication
284
+ # is implemented.
285
+ false
286
+ end
264
287
 
265
- # For now this just returns the `@wikibase_url`, but in the future the API
266
- # routes will presumably be nested further, so this is just future-proofing
267
- # to allow that to be easily changed later.
268
- #
269
- # @return [String] URL for the Wikibase API endpoint.
270
- def api_url
271
- @api_url ||= "#{@wikibase_url}/w/rest.php/wikibase/v0"
272
- end
288
+ # Does the current instance of Client allow anonymous IP-based edits?
289
+ #
290
+ # @return [Boolean]
291
+ def allow_ip_edits?
292
+ @allow_ip_edits
293
+ end
273
294
 
274
- # Default headers to be sent with every request.
275
- #
276
- # @return [Hash] A hash of some headers that should be used when sending a request.
277
- def universal_headers
278
- @universal_headers ||= {
279
- 'User-Agent' => @user_agent,
280
- 'Content-Type' => 'application/json'
281
- }
282
- end
295
+ private
283
296
 
284
- # Make a GET request to a given Wikibase endpoint.
285
- #
286
- # @param path [String] The relative path for the API endpoint.
287
- # @param params [Hash] Query parameters to send with the request, if any.
288
- # @return [Hash] JSON response, parsed into a hash.
289
- def get_request(path, params = nil)
290
- url = "#{api_url}#{path}"
291
-
292
- response = Faraday.get(url, params, universal_headers)
293
-
294
- # Error handling if it doesn't return a 200
295
- unless response.success?
296
- puts 'Something went wrong with this request!'
297
- puts "Status Code: #{response.status}"
298
- puts response.body.inspect
297
+ # For now this just returns the `@wikibase_url`, but in the future the API
298
+ # routes will presumably be nested further, so this is just future-proofing
299
+ # to allow that to be easily changed later.
300
+ #
301
+ # @return [String] URL for the Wikibase API endpoint.
302
+ def api_url
303
+ @api_url ||= "#{@wikibase_url}/w/rest.php/wikibase/v0"
299
304
  end
300
305
 
301
- JSON.parse(response.body)
302
- end
303
-
304
- # Make a POST request to a given Wikibase endpoint.
305
- #
306
- # @param path [String] The relative path for the API endpoint.
307
- # @param body [Hash] The body to post to the endpoint.
308
- # @param tags [Array<String>] The tags to apply to the edit being made by this request, for PUT/POST/DELETE requests.
309
- # @param comment [String] The edit description, for PUT/POST/DELETE requests.
310
- # @return [Hash] JSON response, parsed into a hash.
311
- def post_request(path, body = {}, tags: nil, comment: nil)
312
- url = "#{api_url}#{path}"
313
-
314
- body[:bot] = @bot
315
- body[:tags] = tags unless tags.empty?
316
- body[:comment] = comment unless comment.nil?
317
-
318
- response = Faraday.post(url) do |req|
319
- req.body = JSON.generate(body)
320
- req.headers = universal_headers
306
+ # Default headers to be sent with every request.
307
+ #
308
+ # @return [Hash] A hash of some headers that should be used when sending a request.
309
+ def universal_headers
310
+ @universal_headers ||= {
311
+ 'User-Agent' => @user_agent,
312
+ 'Content-Type' => 'application/json'
313
+ }
321
314
  end
322
315
 
323
- puts response.body.inspect if ENV['DEBUG']
316
+ # Make a GET request to a given Wikibase endpoint.
317
+ #
318
+ # @param path [String] The relative path for the API endpoint.
319
+ # @param params [Hash] Query parameters to send with the request, if any.
320
+ # @return [Hash] JSON response, parsed into a hash.
321
+ def get_request(path, params = nil)
322
+ url = "#{api_url}#{path}"
324
323
 
325
- response
326
- end
324
+ response = Faraday.get(url, params, universal_headers)
325
+
326
+ # Error handling if it doesn't return a 200
327
+ unless response.success?
328
+ puts 'Something went wrong with this request!'
329
+ puts "Status Code: #{response.status}"
330
+ puts response.body.inspect
331
+ end
327
332
 
328
- # Make a DELETE request to a given Wikibase endpoint.
329
- #
330
- # @param path [String] The relative path for the API endpoint.
331
- # @param tags [Array<String>] The tags to apply to the edit being made by this request, for PUT/POST/DELETE requests.
332
- # @param comment [String] The edit description, for PUT/POST/DELETE requests.
333
- # @return [Hash] JSON response, parsed into a hash.
334
- def delete_request(path, tags: [], comment: nil)
335
- url = "#{api_url}#{path}"
336
-
337
- body = {}
338
- body[:bot] = @bot
339
- body[:tags] = tags unless tags.empty?
340
- body[:comment] = comment unless comment.nil?
341
-
342
- response = Faraday.delete(url) do |req|
343
- req.body = JSON.generate(body)
344
- req.headers = universal_headers
333
+ JSON.parse(response.body)
345
334
  end
346
335
 
347
- puts response.body.inspect if ENV['DEBUG']
336
+ # Make a POST request to a given Wikibase endpoint.
337
+ #
338
+ # @param path [String] The relative path for the API endpoint.
339
+ # @param body [Hash] The body to post to the endpoint.
340
+ # @param tags [Array<String>] The tags to apply to the edit being made by this request, for PUT/POST/DELETE requests.
341
+ # @param comment [String] The edit description, for PUT/POST/DELETE requests.
342
+ # @return [Hash] JSON response, parsed into a hash.
343
+ def post_request(path, body = {}, tags: [], comment: nil)
344
+ ensure_edit_permitted!
345
+
346
+ url = "#{api_url}#{path}"
347
+
348
+ body[:bot] = @bot
349
+ body[:tags] = tags unless tags.empty?
350
+ body[:comment] = comment unless comment.nil?
351
+
352
+ response = Faraday.post(url) do |req|
353
+ req.body = JSON.generate(body)
354
+ req.headers = universal_headers
355
+ end
356
+
357
+ puts response.body.inspect if ENV['DEBUG']
358
+
359
+ # Error handling if it doesn't return a 200
360
+ unless response.success?
361
+ puts 'Something went wrong with this request!'
362
+ puts "Status Code: #{response.status}"
363
+ puts response.body.inspect
364
+ end
365
+
366
+ response
367
+ end
348
368
 
349
- # Error handling if it doesn't return a 200
350
- unless response.success?
351
- puts 'Something went wrong with this request!'
352
- puts "Status Code: #{response.status}"
353
- puts response.body.inspect
369
+ # Make a DELETE request to a given Wikibase endpoint.
370
+ #
371
+ # @param path [String] The relative path for the API endpoint.
372
+ # @param tags [Array<String>] The tags to apply to the edit being made by this request, for PUT/POST/DELETE requests.
373
+ # @param comment [String] The edit description, for PUT/POST/DELETE requests.
374
+ # @return [Hash] JSON response, parsed into a hash.
375
+ def delete_request(path, tags: [], comment: nil)
376
+ ensure_edit_permitted!
377
+
378
+ url = "#{api_url}#{path}"
379
+
380
+ body = {}
381
+ body[:bot] = @bot
382
+ body[:tags] = tags unless tags.empty?
383
+ body[:comment] = comment unless comment.nil?
384
+
385
+ response = Faraday.delete(url) do |req|
386
+ req.body = JSON.generate(body)
387
+ req.headers = universal_headers
388
+ end
389
+
390
+ puts response.body.inspect if ENV['DEBUG']
391
+
392
+ # Error handling if it doesn't return a 200
393
+ unless response.success?
394
+ puts 'Something went wrong with this request!'
395
+ puts "Status Code: #{response.status}"
396
+ puts response.body.inspect
397
+ end
398
+
399
+ response
354
400
  end
355
401
 
356
- response
357
- end
402
+ # Coerce an Item ID in the formats 'Q123', '123' or 123 into a consistent
403
+ # 'Q123' format. We need to have the ID in the format 'Q123' for the API
404
+ # request, which is why coercion is necessary.
405
+ #
406
+ # @param id [String, Integer]
407
+ # @return [String]
408
+ def coerce_item_id(id)
409
+ return id if id.to_s.start_with?('Q')
410
+
411
+ "Q#{id}"
412
+ end
358
413
 
359
- # Coerce an Item ID in the formats 'Q123', '123' or 123 into a consistent
360
- # 'Q123' format. We need to have the ID in the format 'Q123' for the API
361
- # request, which is why coercion is necessary.
362
- #
363
- # @param id [String, Integer]
364
- # @return [String]
365
- def coerce_item_id(id)
366
- return id if id.to_s.start_with?('Q')
414
+ # Coerce a Property ID in the formats 'P123', '123' or 123 into a consistent
415
+ # 'P123' format. We need to have the ID in the format 'P123' for the API
416
+ # request, which is why coercion is necessary.
417
+ #
418
+ # @param property_id [String, Integer]
419
+ # @return [String]
420
+ def coerce_property_id(property_id)
421
+ return property_id if property_id.to_s.start_with?('P')
367
422
 
368
- "Q#{id}"
423
+ "P#{property_id}"
424
+ end
425
+
426
+ # Check if authentication has been provided, and then check if IP edits
427
+ # are allowed. If neither condition returns true, raise an error.
428
+ #
429
+ # @return [void]
430
+ # @raise [DisallowedIpEditError]
431
+ def ensure_edit_permitted!
432
+ return if authenticated?
433
+ return if allow_ip_edits?
434
+
435
+ raise DisallowedIpEditError
436
+ end
369
437
  end
370
438
  end