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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -2
- data/README.md +24 -2
- data/bin/console +0 -4
- data/lib/wikidatum/client.rb +395 -327
- data/lib/wikidatum/data_type/base.rb +61 -0
- data/lib/wikidatum/{data_value_type → data_type}/globe_coordinate.rb +21 -20
- data/lib/wikidatum/{data_value_type → data_type}/monolingual_text.rb +19 -18
- data/lib/wikidatum/{data_value_type → data_type}/no_value.rb +3 -11
- data/lib/wikidatum/data_type/quantity.rb +75 -0
- data/lib/wikidatum/{data_value_type → data_type}/some_value.rb +3 -11
- data/lib/wikidatum/{data_value_type → data_type}/time.rb +20 -37
- data/lib/wikidatum/data_type/wikibase_item.rb +64 -0
- data/lib/wikidatum/{data_value_type → data_type}/wikibase_string.rb +16 -15
- data/lib/wikidatum/data_type.rb +11 -0
- data/lib/wikidatum/item.rb +2 -2
- data/lib/wikidatum/qualifier.rb +52 -4
- data/lib/wikidatum/reference.rb +9 -9
- data/lib/wikidatum/reference_part.rb +56 -0
- data/lib/wikidatum/statement.rb +21 -11
- data/lib/wikidatum/utils.rb +25 -0
- data/lib/wikidatum/version.rb +1 -1
- data/lib/wikidatum.rb +31 -20
- data/wikidatum.gemspec +1 -1
- metadata +15 -17
- data/Gemfile +0 -13
- data/Gemfile.lock +0 -73
- data/Rakefile +0 -16
- data/lib/wikidatum/data_value_type/base.rb +0 -61
- data/lib/wikidatum/data_value_type/quantity.rb +0 -92
- data/lib/wikidatum/data_value_type/wikibase_entity_id.rb +0 -83
- data/lib/wikidatum/data_value_type.rb +0 -11
- data/lib/wikidatum/snak.rb +0 -79
data/lib/wikidatum/client.rb
CHANGED
@@ -3,368 +3,436 @@
|
|
3
3
|
require 'faraday'
|
4
4
|
require 'faraday/net_http'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
87
|
+
id = coerce_item_id(id)
|
65
88
|
|
66
|
-
|
89
|
+
response = get_request("/entities/items/#{id}")
|
67
90
|
|
68
|
-
|
91
|
+
puts JSON.pretty_generate(response) if ENV['DEBUG']
|
69
92
|
|
70
|
-
|
71
|
-
|
93
|
+
Wikidatum::Item.marshal_load(response)
|
94
|
+
end
|
72
95
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
106
|
+
response = get_request("/statements/#{id}")
|
84
107
|
|
85
|
-
|
108
|
+
puts JSON.pretty_generate(response) if ENV['DEBUG']
|
86
109
|
|
87
|
-
|
88
|
-
|
110
|
+
Wikidatum::Statement.marshal_load(response)
|
111
|
+
end
|
89
112
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
227
|
+
when 'Wikidatum::DataType::SomeValue'
|
228
|
+
statement_hash = {
|
229
|
+
property: {
|
230
|
+
id: property
|
231
|
+
},
|
232
|
+
value: {
|
233
|
+
type: 'somevalue'
|
234
|
+
}
|
214
235
|
}
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
248
|
+
body = { statement: statement_hash.merge({ qualifiers: qualifiers, references: references, rank: rank.to_s, type: 'statement' }) }
|
233
249
|
|
234
|
-
|
250
|
+
response = post_request("/entities/items/#{id}/statements", body, tags: tags, comment: comment)
|
235
251
|
|
236
|
-
|
252
|
+
puts JSON.pretty_generate(response) if ENV['DEBUG']
|
237
253
|
|
238
|
-
|
239
|
-
|
254
|
+
response.success?
|
255
|
+
end
|
240
256
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
|
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
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
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
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
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
|
-
|
326
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
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
|
-
|
357
|
-
|
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
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
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
|