radio5 0.1.1 → 0.2.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 +12 -0
- data/README.md +130 -21
- data/lib/radio5/api.rb +3 -0
- data/lib/radio5/client/islands.rb +13 -7
- data/lib/radio5/client/tracks.rb +4 -2
- data/lib/radio5/client/users.rb +134 -2
- data/lib/radio5/client.rb +1 -1
- data/lib/radio5/http.rb +41 -35
- data/lib/radio5/regexps.rb +15 -3
- data/lib/radio5/utils.rb +27 -4
- data/lib/radio5/validator.rb +89 -27
- data/lib/radio5/version.rb +1 -1
- data/lib/radio5.rb +14 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: afc335b6a9726ff30f27942868b19513b02aea353144fcd8f283bc8da3fe1159
|
|
4
|
+
data.tar.gz: 41852476ae434f3718e68d2c60d8fc7b868425288efeae519024d766e7d06b94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8be48ecc1b8681d7749775473b9b3af13aa469aa3098a2896ffbb026056b696d3135924b19d1ff4ff79c9e6e50a6c05cb9f270372874b52c2df48401d504d72e
|
|
7
|
+
data.tar.gz: 639fab8c48fe128066118ebcf79e72baf3aa6ef5d104540b03bbc30d51b3f77163d9cd348fa52cc6c2b23e75027cd224d77d42ba80f7efe288d067c112628781
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
0.2.0
|
|
2
|
+
----------
|
|
3
|
+
|
|
4
|
+
- Added users endpoints support
|
|
5
|
+
- Replaced `track[:cover]` with `track[:cover_url]` with extended format (per size) as with users
|
|
6
|
+
|
|
7
|
+
0.1.2
|
|
8
|
+
----------
|
|
9
|
+
|
|
10
|
+
- Fixed bug when custom HTTP config was ignored
|
|
11
|
+
- Fixed bug with broken HTTP retries
|
|
12
|
+
|
|
1
13
|
0.1.1
|
|
2
14
|
----------
|
|
3
15
|
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Radio5
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/radio5)
|
|
4
|
-
[](https://github.com/ocvit/radio5/actions)
|
|
5
5
|
[](https://coveralls.io/github/ocvit/radio5?branch=main)
|
|
6
6
|
|
|
7
7
|
Adapter for [Radiooooo](https://radiooooo.com/) private API.
|
|
@@ -47,7 +47,7 @@ client = Radio5::Client.new(
|
|
|
47
47
|
)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Tracks
|
|
51
51
|
|
|
52
52
|
To get random track:
|
|
53
53
|
|
|
@@ -64,7 +64,12 @@ client.random_track
|
|
|
64
64
|
# songwriter: "Bart Howard",
|
|
65
65
|
# length: 133,
|
|
66
66
|
# info: "It is the original recording of Fly me to the moon !",
|
|
67
|
-
# cover_url:
|
|
67
|
+
# cover_url: {
|
|
68
|
+
# thumb: "https://asset.radiooooo.com/cover/USA/1950/thumb/<uuid>_1.jpg",
|
|
69
|
+
# small: "https://asset.radiooooo.com/cover/USA/1950/small/<uuid>_1.jpg",
|
|
70
|
+
# medium: "https://asset.radiooooo.com/cover/USA/1950/medium/<uuid>_1.jpg",
|
|
71
|
+
# large: "https://asset.radiooooo.com/cover/USA/1950/large/<uuid>_1.jpg",
|
|
72
|
+
# },
|
|
68
73
|
# audio: {
|
|
69
74
|
# mpeg: {
|
|
70
75
|
# url: "https://radiooooo-track.b-cdn.net/USA/1950/<uuid>.mp3?token=<token>&expires=1704717060",
|
|
@@ -120,12 +125,12 @@ client.track("655f7bb24b0d722a021a2cf2")
|
|
|
120
125
|
# output is exactly the same as from `#random_track`, but `created_at` is now filled
|
|
121
126
|
```
|
|
122
127
|
|
|
128
|
+
## Countries / decades / moods
|
|
129
|
+
|
|
123
130
|
OK, what input parameters are available?
|
|
124
131
|
|
|
125
132
|
```ruby
|
|
126
|
-
#
|
|
127
|
-
# - `exist` - "is it still around" flag
|
|
128
|
-
# - `rank` - subjective ranking provided by the website, only 10 countries have it
|
|
133
|
+
# countries
|
|
129
134
|
client.countries
|
|
130
135
|
# => {
|
|
131
136
|
# "AFG" => {name: "Afganistan", exist: true, rank: nil},
|
|
@@ -133,6 +138,10 @@ client.countries
|
|
|
133
138
|
# "FRA" => {name: "France", exist: true, rank: 2},
|
|
134
139
|
# ...
|
|
135
140
|
# }
|
|
141
|
+
#
|
|
142
|
+
# NOTES:
|
|
143
|
+
# - `exist` - "is it still around" flag
|
|
144
|
+
# - `rank` - subjective ranking provided by the website, only 10 countries have it
|
|
136
145
|
|
|
137
146
|
# decades
|
|
138
147
|
client.decades
|
|
@@ -142,10 +151,10 @@ client.decades
|
|
|
142
151
|
client.moods
|
|
143
152
|
# => [:fast, :slow, :weird]
|
|
144
153
|
#
|
|
145
|
-
# NOTE:
|
|
154
|
+
# NOTE: all 3 moods are used in `#random_track` and `#island_track` by default
|
|
146
155
|
```
|
|
147
156
|
|
|
148
|
-
It's also possible to get all valid `country
|
|
157
|
+
It's also possible to get all valid `country`/ `moods` combinations per for specific decade in advance:
|
|
149
158
|
|
|
150
159
|
```ruby
|
|
151
160
|
# grouped by country
|
|
@@ -168,10 +177,13 @@ client.countries_for_decade(1960, group_by: :mood)
|
|
|
168
177
|
# }
|
|
169
178
|
```
|
|
170
179
|
|
|
171
|
-
|
|
180
|
+
## Islands
|
|
181
|
+
|
|
182
|
+
Islands work as a kind of thematic collections.
|
|
183
|
+
|
|
184
|
+
To get a list of all islands:
|
|
172
185
|
|
|
173
186
|
```ruby
|
|
174
|
-
# list all islands
|
|
175
187
|
client.islands
|
|
176
188
|
# => [{
|
|
177
189
|
# id: "5d330a3e06fb03d8872a3316",
|
|
@@ -208,22 +220,119 @@ client.islands
|
|
|
208
220
|
# - `play_mode` - it is somehow used in a web app
|
|
209
221
|
# - `created_by` - `id` of user who created this island
|
|
210
222
|
# - `updated_by` - ...and who updated it last time
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
To get random track from selected island:
|
|
211
226
|
|
|
212
|
-
|
|
227
|
+
```ruby
|
|
213
228
|
client.island_track(island_id: "5d330a3e06fb03d8872a3316")
|
|
214
229
|
|
|
215
230
|
# it's also possible to specify moods
|
|
216
231
|
client.island_track(island_id: "5d330a3e06fb03d8872a3316", moods: [:fast, :weird])
|
|
217
232
|
```
|
|
218
233
|
|
|
219
|
-
|
|
234
|
+
## Users
|
|
235
|
+
|
|
236
|
+
To get information about specific user using its `id`:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
client.user("5d3306de06fb03d8871fd138")
|
|
240
|
+
# => {
|
|
241
|
+
# id: "5d3306de06fb03d8871fd138",
|
|
242
|
+
# uuid: <uuid>,
|
|
243
|
+
# name: "Paul Charmant-Kabil",
|
|
244
|
+
# info: "Dreamseeker",
|
|
245
|
+
# country: "FRA",
|
|
246
|
+
# rank: 9188,
|
|
247
|
+
# image_url: {
|
|
248
|
+
# icon: "https://asset.radiooooo.com/user/1409/icon/<uuid>_3.jpg",
|
|
249
|
+
# thumb: "https://asset.radiooooo.com/user/1409/thumb/<uuid>_3.jpg",
|
|
250
|
+
# small: "https://asset.radiooooo.com/user/1409/small/<uuid>_3.jpg",
|
|
251
|
+
# medium: "https://asset.radiooooo.com/user/1409/medium/<uuid>_3.jpg",
|
|
252
|
+
# large: "https://asset.radiooooo.com/user/1409/large/<uuid>_3.jpg"
|
|
253
|
+
# },
|
|
254
|
+
# birthday: {
|
|
255
|
+
# time: 1981-01-31 23:01:01 UTC,
|
|
256
|
+
# year_normalized: 1981
|
|
257
|
+
# },
|
|
258
|
+
# created_at: 2014-09-30 18:58:32 UTC
|
|
259
|
+
# }
|
|
260
|
+
#
|
|
261
|
+
# NOTES:
|
|
262
|
+
# - `rank` - is not unique
|
|
263
|
+
# - `birthday`:
|
|
264
|
+
# - `time` - original time from API, it is always around first day of Jan or last day of
|
|
265
|
+
# December, with a strange hour offset around midnight, so it looks like the
|
|
266
|
+
# only real value here is the year
|
|
267
|
+
# - `year_normalized` - de-offset'ed year
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
To get user followers or followings counts:
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
client.user_follow_counts("5d3306de06fb03d8871fd138")
|
|
274
|
+
# => {
|
|
275
|
+
# followings: 17,
|
|
276
|
+
# followers: 866
|
|
277
|
+
# }
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
To get list of user followers:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
# all followers will be returned by default
|
|
284
|
+
client.user_followers("5d3306de06fb03d8871fd138")
|
|
285
|
+
# => [{
|
|
286
|
+
# id: "5f8f175051430765bd5c1b08",
|
|
287
|
+
# name: "Philart",
|
|
288
|
+
# country: "FRA",
|
|
289
|
+
# rank: 25,
|
|
290
|
+
# image_url: {
|
|
291
|
+
# icon: "https://asset.radiooooo.com/user/2010/icon/<uuid>_1.jpg",
|
|
292
|
+
# thumb: "https://asset.radiooooo.com/user/2010/thumb/<uuid>_1.jpg",
|
|
293
|
+
# small: "https://asset.radiooooo.com/user/2010/small/<uuid>_1.jpg",
|
|
294
|
+
# medium: "https://asset.radiooooo.com/user/2010/medium/<uuid>_1.jpg",
|
|
295
|
+
# large: "https://asset.radiooooo.com/user/2010/large/<uuid>_1.jpg"
|
|
296
|
+
# },
|
|
297
|
+
# created_at: 2020-10-20 16:58:56.819 UTC
|
|
298
|
+
# }, ...]
|
|
299
|
+
|
|
300
|
+
# it's also possible to specify size/page
|
|
301
|
+
client.user_followers("5d3306de06fb03d8871fd138", size: 1, page: 5)
|
|
302
|
+
# => [{...}]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
To get list of user followings:
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
# all followings will be returned by default
|
|
309
|
+
client.user_followings("5d3306de06fb03d8871fd138")
|
|
310
|
+
# => [{
|
|
311
|
+
# id: "640ab0cebf47667afdbf9edb",
|
|
312
|
+
# name: "Cap Jones",
|
|
313
|
+
# country: "USA",
|
|
314
|
+
# rank: 5,
|
|
315
|
+
# image_url: {
|
|
316
|
+
# icon: "https://asset.radiooooo.com/user/2303/icon/<uuid>_1.jpg",
|
|
317
|
+
# thumb: "https://asset.radiooooo.com/user/2303/thumb/<uuid>_1.jpg",
|
|
318
|
+
# small: "https://asset.radiooooo.com/user/2303/small/<uuid>_1.jpg",
|
|
319
|
+
# medium: "https://asset.radiooooo.com/user/2303/medium/<uuid>_1.jpg",
|
|
320
|
+
# large: "https://asset.radiooooo.com/user/2303/large/<uuid>_1.jpg"
|
|
321
|
+
# },
|
|
322
|
+
# created_at: 2023-03-10 04:23:42.87 UTC
|
|
323
|
+
# }, ...]
|
|
324
|
+
|
|
325
|
+
# it's also possible to specify size/page
|
|
326
|
+
client.user_followings("5d3306de06fb03d8871fd138", size: 1, page: 5)
|
|
327
|
+
# => [{...}]
|
|
328
|
+
```
|
|
220
329
|
|
|
221
330
|
## Auth?
|
|
222
331
|
|
|
223
|
-
There is just a couple of features that require login
|
|
332
|
+
There is just a couple of features that require login (free or premium account):
|
|
224
333
|
|
|
225
|
-
-
|
|
226
|
-
- `followed` flag
|
|
334
|
+
- `#track_history` - list of tracks you "listened" via `#random_track` or `#island_track` (free)
|
|
335
|
+
- `user[:followed]` flag - indicates whether or not you follow this user (free)
|
|
227
336
|
- `#user_liked_tracks` - list of tracks which user really vibed to (free)
|
|
228
337
|
- ability to use multiple countries as a filter in `#random_track` (premium)
|
|
229
338
|
|
|
@@ -236,17 +345,17 @@ Currently auth is in a WIP state.
|
|
|
236
345
|
- [x] Countries support
|
|
237
346
|
- [x] Islands support
|
|
238
347
|
- [x] Tracks support
|
|
239
|
-
- [
|
|
348
|
+
- [x] Users support
|
|
240
349
|
- [ ] Auth + auth'ed endpoints
|
|
241
350
|
|
|
242
351
|
## Development
|
|
243
352
|
|
|
244
353
|
```sh
|
|
245
|
-
bin/setup
|
|
246
|
-
bin/console
|
|
247
|
-
rake spec
|
|
248
|
-
rake rubocop
|
|
249
|
-
sudo rm -rf /
|
|
354
|
+
bin/setup // install deps
|
|
355
|
+
bin/console // interactive prompt to play around
|
|
356
|
+
rake spec // test!
|
|
357
|
+
rake rubocop // lint!
|
|
358
|
+
sudo rm -rf / // just kidding ^^
|
|
250
359
|
```
|
|
251
360
|
|
|
252
361
|
## Contributing
|
data/lib/radio5/api.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Radio5
|
|
|
5
5
|
class Error < StandardError; end
|
|
6
6
|
class TrackNotFound < Error; end
|
|
7
7
|
class MatchingTrackNotFound < Error; end
|
|
8
|
+
class UserNotFound < Error; end
|
|
8
9
|
class UnexpectedResponse < StandardError; end
|
|
9
10
|
|
|
10
11
|
HOST = "radiooooo.com"
|
|
@@ -39,6 +40,8 @@ module Radio5
|
|
|
39
40
|
raise TrackNotFound
|
|
40
41
|
in error: "No track for this selection"
|
|
41
42
|
raise MatchingTrackNotFound
|
|
43
|
+
in error: "No info for this user"
|
|
44
|
+
raise UserNotFound
|
|
42
45
|
in error: other_error
|
|
43
46
|
raise Error, other_error
|
|
44
47
|
else
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
module Radio5
|
|
4
4
|
class Client
|
|
5
5
|
module Islands
|
|
6
|
-
include Utils
|
|
7
|
-
|
|
8
|
-
# rubocop:disable Layout/HashAlignment
|
|
9
6
|
def islands
|
|
10
7
|
_, json = api.get("/island/all")
|
|
11
8
|
|
|
12
9
|
json.map do |island|
|
|
10
|
+
Parser.island_info(island)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Parser
|
|
15
|
+
extend Utils
|
|
16
|
+
|
|
17
|
+
# rubocop:disable Layout/HashAlignment
|
|
18
|
+
def self.island_info(island)
|
|
13
19
|
rank_value = island[:sort]
|
|
14
20
|
rank = rank_value if rank_value.is_a?(Integer)
|
|
15
21
|
|
|
@@ -30,9 +36,9 @@ module Radio5
|
|
|
30
36
|
favourite_count: island[:favorites],
|
|
31
37
|
play_count: island.fetch(:plays),
|
|
32
38
|
rank: rank,
|
|
33
|
-
icon_url: parse_asset_url(island
|
|
34
|
-
splash_url: parse_asset_url(island
|
|
35
|
-
marker_url: parse_asset_url(island
|
|
39
|
+
icon_url: parse_asset_url(island[:icon]),
|
|
40
|
+
splash_url: parse_asset_url(island[:splash]),
|
|
41
|
+
marker_url: parse_asset_url(island[:marker]),
|
|
36
42
|
enabled: island.fetch(:enabled),
|
|
37
43
|
free: island[:free],
|
|
38
44
|
on_map: island.fetch(:onmap),
|
|
@@ -44,8 +50,8 @@ module Radio5
|
|
|
44
50
|
updated_by: updated_by
|
|
45
51
|
}
|
|
46
52
|
end
|
|
53
|
+
# rubocop:enable Layout/HashAlignment
|
|
47
54
|
end
|
|
48
|
-
# rubocop:enable Layout/HashAlignment
|
|
49
55
|
end
|
|
50
56
|
end
|
|
51
57
|
end
|
data/lib/radio5/client/tracks.rb
CHANGED
|
@@ -69,6 +69,9 @@ module Radio5
|
|
|
69
69
|
created_at = created_node && parse_time_string(created_node.fetch(:date))
|
|
70
70
|
created_by = created_node ? created_node.fetch(:user_id) : json.fetch(:profile_id)
|
|
71
71
|
|
|
72
|
+
cover_node = json[:image] || json[:cover]
|
|
73
|
+
cover_url = parse_image_urls(cover_node, entity: :track)
|
|
74
|
+
|
|
72
75
|
audio = {
|
|
73
76
|
mpeg: track_audio(json, :mpeg),
|
|
74
77
|
ogg: track_audio(json, :ogg)
|
|
@@ -85,7 +88,7 @@ module Radio5
|
|
|
85
88
|
songwriter: normalize_string(json[:songwriter]),
|
|
86
89
|
length: json.fetch(:length),
|
|
87
90
|
info: normalize_string(json[:info]),
|
|
88
|
-
cover_url:
|
|
91
|
+
cover_url: cover_url,
|
|
89
92
|
audio: audio,
|
|
90
93
|
decade: json.fetch(:decade),
|
|
91
94
|
mood: symbolize_mood(json.fetch(:mood)),
|
|
@@ -110,7 +113,6 @@ module Radio5
|
|
|
110
113
|
}
|
|
111
114
|
end
|
|
112
115
|
end
|
|
113
|
-
private_constant :Parser
|
|
114
116
|
end
|
|
115
117
|
end
|
|
116
118
|
end
|
data/lib/radio5/client/users.rb
CHANGED
|
@@ -3,9 +3,141 @@
|
|
|
3
3
|
module Radio5
|
|
4
4
|
class Client
|
|
5
5
|
module Users
|
|
6
|
-
def user
|
|
7
|
-
|
|
6
|
+
def user(id)
|
|
7
|
+
validate_user_id!(id)
|
|
8
|
+
|
|
9
|
+
_, json = api.get("/contributor/#{id}")
|
|
10
|
+
|
|
11
|
+
Parser.user_info(json)
|
|
12
|
+
rescue Api::UserNotFound
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def user_tracks(id, status: :on_air, size: MAX_PAGE_SIZE, page: 1)
|
|
17
|
+
validate_user_id!(id)
|
|
18
|
+
validate_user_track_status!(status)
|
|
19
|
+
validate_page_size!(size)
|
|
20
|
+
validate_page_number!(page)
|
|
21
|
+
|
|
22
|
+
query_params = {
|
|
23
|
+
status: stringify_user_track_status(status),
|
|
24
|
+
size: size,
|
|
25
|
+
page: page
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_, json = api.get("/contributor/uploaded/#{id}", query_params: query_params)
|
|
29
|
+
|
|
30
|
+
json.map do |track|
|
|
31
|
+
Parser.user_track_info(track)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def user_follow_counts(id)
|
|
36
|
+
validate_user_id!(id)
|
|
37
|
+
|
|
38
|
+
_, json = api.get("/follow/count/#{id}")
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
followings: json.fetch(:following),
|
|
42
|
+
followers: json.fetch(:followers)
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def user_followers(id, size: MAX_PAGE_SIZE, page: 1)
|
|
47
|
+
validate_user_id!(id)
|
|
48
|
+
validate_page_size!(size)
|
|
49
|
+
validate_page_number!(page)
|
|
50
|
+
|
|
51
|
+
_, json = api.get("/follow/list/follower/#{id}", query_params: {size: size, page: page})
|
|
52
|
+
|
|
53
|
+
json.map do |user|
|
|
54
|
+
Parser.follow_user_info(user)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def user_followings(id, size: MAX_PAGE_SIZE, page: 1)
|
|
59
|
+
validate_user_id!(id)
|
|
60
|
+
validate_page_size!(size)
|
|
61
|
+
validate_page_number!(page)
|
|
62
|
+
|
|
63
|
+
_, json = api.get("/follow/list/following/#{id}", query_params: {size: size, page: page})
|
|
64
|
+
|
|
65
|
+
json.map do |user|
|
|
66
|
+
Parser.follow_user_info(user)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def user_liked_tracks
|
|
71
|
+
raise NotImplementedError, "depends on auth"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# rubocop:disable Layout/HashAlignment
|
|
75
|
+
module Parser
|
|
76
|
+
extend Utils
|
|
77
|
+
|
|
78
|
+
def self.user_info(json)
|
|
79
|
+
birthday = if json[:birthday]
|
|
80
|
+
time = parse_time_string(json[:birthday])
|
|
81
|
+
year_normalized = normalize_year(time)
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
time: time,
|
|
85
|
+
year_normalized: year_normalized
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
id: json.fetch(:_id),
|
|
91
|
+
uuid: json.fetch(:uuid),
|
|
92
|
+
name: normalize_string(json.fetch(:pseudonym)),
|
|
93
|
+
info: normalize_string(json[:info]),
|
|
94
|
+
country: json[:country],
|
|
95
|
+
rank: json.fetch(:ranking),
|
|
96
|
+
image_url: parse_image_urls(json[:image], entity: :user),
|
|
97
|
+
birthday: birthday,
|
|
98
|
+
created_at: parse_time_string(json.fetch(:created))
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.user_track_info(track)
|
|
103
|
+
cover_node = track[:image] || track[:cover]
|
|
104
|
+
cover_url = parse_image_urls(cover_node, entity: :track)
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
id: track.fetch(:_id),
|
|
108
|
+
uuid: track.fetch(:uuid),
|
|
109
|
+
artist: normalize_string(track.fetch(:artist)),
|
|
110
|
+
title: normalize_string(track.fetch(:title)),
|
|
111
|
+
year: normalize_string(track.fetch(:year)),
|
|
112
|
+
cover_url: cover_url,
|
|
113
|
+
decade: track.fetch(:decade),
|
|
114
|
+
country: track.fetch(:country),
|
|
115
|
+
like_count: track.fetch(:likes),
|
|
116
|
+
status: symbolize_user_track_status(track[:status])
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# TODO: strange name tbh, change later
|
|
121
|
+
def self.follow_user_info(user)
|
|
122
|
+
{
|
|
123
|
+
id: user.fetch(:_id),
|
|
124
|
+
name: normalize_string(user.fetch(:pseudonym)),
|
|
125
|
+
country: user[:country],
|
|
126
|
+
rank: user.fetch(:ranking),
|
|
127
|
+
image_url: parse_image_urls(user[:image], entity: :user),
|
|
128
|
+
created_at: parse_time_string(user.fetch(:created))
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.normalize_year(time)
|
|
133
|
+
if time.month == 12
|
|
134
|
+
time.year + 1
|
|
135
|
+
else
|
|
136
|
+
time.year
|
|
137
|
+
end
|
|
138
|
+
end
|
|
8
139
|
end
|
|
140
|
+
# rubocop:enable Layout/HashAlignment
|
|
9
141
|
end
|
|
10
142
|
end
|
|
11
143
|
end
|
data/lib/radio5/client.rb
CHANGED
data/lib/radio5/http.rb
CHANGED
|
@@ -9,12 +9,15 @@ module Radio5
|
|
|
9
9
|
DEFAULT_OPEN_TIMEOUT = 10 # seconds
|
|
10
10
|
DEFAULT_READ_TIMEOUT = 10 # seconds
|
|
11
11
|
DEFAULT_WRITE_TIMEOUT = 10 # seconds
|
|
12
|
-
|
|
12
|
+
DEFAULT_PROXY_URL = nil
|
|
13
13
|
DEFAULT_MAX_RETRIES = 3
|
|
14
|
+
DEFAULT_DEBUG_OUTPUT = nil
|
|
15
|
+
|
|
14
16
|
DEFAULT_HEADERS = {
|
|
15
17
|
"Content-Type" => "application/json; charset=utf-8",
|
|
16
18
|
"User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
17
19
|
}.freeze
|
|
20
|
+
|
|
18
21
|
RETRIABLE_ERRORS = [
|
|
19
22
|
Errno::ECONNREFUSED,
|
|
20
23
|
Errno::ECONNRESET,
|
|
@@ -25,41 +28,45 @@ module Radio5
|
|
|
25
28
|
OpenSSL::SSL::SSLError
|
|
26
29
|
].freeze
|
|
27
30
|
|
|
31
|
+
attr_reader :host, :port, :open_timeout, :read_timeout, :write_timeout, :proxy_url, :max_retries, :debug_output, :http_client
|
|
32
|
+
|
|
28
33
|
# rubocop:disable Layout/ExtraSpacing
|
|
29
34
|
def initialize(
|
|
30
35
|
host:,
|
|
31
36
|
port:,
|
|
32
|
-
open_timeout:
|
|
33
|
-
read_timeout:
|
|
34
|
-
write_timeout:
|
|
37
|
+
open_timeout: nil,
|
|
38
|
+
read_timeout: nil,
|
|
39
|
+
write_timeout: nil,
|
|
35
40
|
proxy_url: nil,
|
|
36
|
-
max_retries:
|
|
37
|
-
debug_output:
|
|
41
|
+
max_retries: nil,
|
|
42
|
+
debug_output: nil
|
|
38
43
|
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@
|
|
49
|
-
|
|
50
|
-
@
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
c.
|
|
54
|
-
c.
|
|
55
|
-
|
|
56
|
-
c.
|
|
57
|
-
|
|
58
|
-
c.set_debug_output(debug_output)
|
|
44
|
+
@host = host
|
|
45
|
+
@port = port
|
|
46
|
+
@open_timeout = open_timeout || DEFAULT_OPEN_TIMEOUT
|
|
47
|
+
@read_timeout = read_timeout || DEFAULT_READ_TIMEOUT
|
|
48
|
+
@write_timeout = write_timeout || DEFAULT_WRITE_TIMEOUT
|
|
49
|
+
@proxy_url = proxy_url || DEFAULT_PROXY_URL
|
|
50
|
+
@max_retries = max_retries || DEFAULT_MAX_RETRIES
|
|
51
|
+
@debug_output = debug_output || DEFAULT_DEBUG_OUTPUT
|
|
52
|
+
|
|
53
|
+
@http_client = Net::HTTP.new(@host, @port, proxy_uri&.host, proxy_uri&.port, proxy_uri&.user, proxy_uri&.password)
|
|
54
|
+
|
|
55
|
+
@http_client.tap do |c|
|
|
56
|
+
c.use_ssl = @port == 443
|
|
57
|
+
c.open_timeout = @open_timeout
|
|
58
|
+
c.read_timeout = @read_timeout
|
|
59
|
+
c.write_timeout = @write_timeout
|
|
60
|
+
|
|
61
|
+
c.set_debug_output(@debug_output)
|
|
59
62
|
end
|
|
60
63
|
end
|
|
61
64
|
# rubocop:enable Layout/ExtraSpacing
|
|
62
65
|
|
|
66
|
+
def proxy_uri
|
|
67
|
+
@proxy_uri ||= parse_proxy_uri
|
|
68
|
+
end
|
|
69
|
+
|
|
63
70
|
def request(http_method_class, path, query_params, body, headers)
|
|
64
71
|
request = build_request(http_method_class, path, query_params, body, headers)
|
|
65
72
|
make_request(request)
|
|
@@ -67,16 +74,15 @@ module Radio5
|
|
|
67
74
|
|
|
68
75
|
private
|
|
69
76
|
|
|
70
|
-
def parse_proxy_uri
|
|
77
|
+
def parse_proxy_uri
|
|
71
78
|
return if proxy_url.nil?
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
case uri = URI(proxy_url)
|
|
81
|
+
when URI::HTTP
|
|
82
|
+
uri
|
|
83
|
+
else
|
|
84
|
+
raise ArgumentError, "Invalid proxy URL: #{proxy_url.inspect}, parsed URI: #{uri.inspect}"
|
|
77
85
|
end
|
|
78
|
-
|
|
79
|
-
proxy_uri
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def build_request(http_method_class, path, query_params, body, headers)
|
|
@@ -109,9 +115,9 @@ module Radio5
|
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
def make_request(request, retries: 0)
|
|
112
|
-
|
|
118
|
+
http_client.request(request)
|
|
113
119
|
rescue *RETRIABLE_ERRORS => error
|
|
114
|
-
if retries <
|
|
120
|
+
if retries < max_retries
|
|
115
121
|
make_request(request, retries: retries + 1)
|
|
116
122
|
else
|
|
117
123
|
raise error
|
data/lib/radio5/regexps.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Radio5
|
|
4
4
|
module Regexps
|
|
5
|
+
include Utils
|
|
6
|
+
|
|
5
7
|
# rubocop:disable Layout/ExtraSpacing
|
|
6
8
|
|
|
7
9
|
MONGO_ID = /^[a-f\d]{24}$/.freeze
|
|
@@ -11,18 +13,28 @@ module Radio5
|
|
|
11
13
|
COUNTRY_ISO_CODE_GENERIC = /([A-Z]{3}|KN1)/.freeze
|
|
12
14
|
COUNTRY_ISO_CODE = /^#{COUNTRY_ISO_CODE_GENERIC}$/.freeze
|
|
13
15
|
|
|
14
|
-
ASSET_URL = lambda do |sub_path, exts|
|
|
16
|
+
ASSET_URL = lambda do |sub_path, exts, size = nil|
|
|
15
17
|
asset_host = Regexp.escape(Utils::ASSET_HOST)
|
|
16
18
|
sub_path = sub_path.is_a?(Regexp) ? sub_path : Regexp.escape(sub_path)
|
|
17
19
|
exts = /(#{exts.join("|")})/
|
|
20
|
+
size = /\/#{size}/ if size
|
|
18
21
|
|
|
19
|
-
/#{asset_host}#{sub_path}\/#{UUID_GENERIC}(_\d+)?\.#{exts}/
|
|
22
|
+
/#{asset_host}#{sub_path}#{size}\/#{UUID_GENERIC}(_\d+)?\.#{exts}/
|
|
20
23
|
end.freeze
|
|
21
24
|
|
|
22
25
|
ISLAND_ICON_URL = ASSET_URL.call("/island/icon", ["png", "svg"]).freeze
|
|
23
26
|
ISLAND_SPLASH_URL = ASSET_URL.call("/island/splash", ["png", "svg"]).freeze
|
|
24
27
|
ISLAND_MARKER_URL = ASSET_URL.call("/island/marker", ["png", "svg"]).freeze
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
TRACK_COVER_URL = IMAGE_SIZES[:track].each_with_object({}) do |image_size, hash|
|
|
30
|
+
url = ASSET_URL.call(/\/cover\/#{COUNTRY_ISO_CODE_GENERIC}\/\d{4}/, ["jpg", "jpeg", "png", "gif"], image_size.to_s)
|
|
31
|
+
hash[image_size] = url
|
|
32
|
+
end.freeze
|
|
33
|
+
|
|
34
|
+
USER_IMAGE_URL = IMAGE_SIZES[:user].each_with_object({}) do |image_size, hash|
|
|
35
|
+
url = ASSET_URL.call(/\/user\/\d+/, ["jpg", "jpeg", "png", "gif"], image_size.to_s)
|
|
36
|
+
hash[image_size] = url
|
|
37
|
+
end.freeze
|
|
26
38
|
|
|
27
39
|
AUDIO_URL = lambda do |exts|
|
|
28
40
|
exts = /(#{exts.join("|")})/
|
data/lib/radio5/utils.rb
CHANGED
|
@@ -9,17 +9,32 @@ module Radio5
|
|
|
9
9
|
|
|
10
10
|
ASSET_HOST = "https://asset.radiooooo.com"
|
|
11
11
|
|
|
12
|
+
IMAGE_SIZES = {
|
|
13
|
+
track: %i[thumb small medium large],
|
|
14
|
+
user: %i[icon thumb small medium large]
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
12
17
|
def parse_json(json_raw)
|
|
13
18
|
JSON.parse(json_raw, symbolize_names: true)
|
|
14
19
|
end
|
|
15
20
|
|
|
16
|
-
def
|
|
17
|
-
node = hash[key]
|
|
18
|
-
|
|
21
|
+
def parse_image_urls(node, entity:)
|
|
19
22
|
if node
|
|
20
23
|
path, filename = node.fetch_values(:path, :filename)
|
|
21
|
-
|
|
24
|
+
image_sizes = IMAGE_SIZES.fetch(entity)
|
|
25
|
+
|
|
26
|
+
image_sizes.each_with_object({}) do |image_size, hash|
|
|
27
|
+
image_size_path = "#{path}#{image_size}/"
|
|
28
|
+
image_url = create_asset_url(image_size_path, filename)
|
|
29
|
+
|
|
30
|
+
hash[image_size] = image_url
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
22
34
|
|
|
35
|
+
def parse_asset_url(node)
|
|
36
|
+
if node
|
|
37
|
+
path, filename = node.fetch_values(:path, :filename)
|
|
23
38
|
create_asset_url(path, filename)
|
|
24
39
|
end
|
|
25
40
|
end
|
|
@@ -50,5 +65,13 @@ module Radio5
|
|
|
50
65
|
def symbolize_mood(mood)
|
|
51
66
|
MOODS_MAPPING.key(mood)
|
|
52
67
|
end
|
|
68
|
+
|
|
69
|
+
def stringify_user_track_status(status)
|
|
70
|
+
USER_TRACK_STATUSES_MAPPING.fetch(status)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def symbolize_user_track_status(status)
|
|
74
|
+
USER_TRACK_STATUSES_MAPPING.key(status)
|
|
75
|
+
end
|
|
53
76
|
end
|
|
54
77
|
end
|
data/lib/radio5/validator.rb
CHANGED
|
@@ -20,63 +20,125 @@ module Radio5
|
|
|
20
20
|
object.is_a?(Symbol) && MOODS_MAPPING.key?(object)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def user_track_status?(object)
|
|
24
|
+
object.is_a?(Symbol) && USER_TRACK_STATUSES_MAPPING.key?(object)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def positive_number?(object)
|
|
28
|
+
object.is_a?(Integer) && object.positive?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate!
|
|
32
|
+
yield
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_user_id!(object)
|
|
38
|
+
validate! do
|
|
39
|
+
unless mongo_id?(object)
|
|
40
|
+
raise ArgumentError, "invalid user ID: #{object.inspect}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
23
45
|
def validate_track_id!(object)
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
validate! do
|
|
47
|
+
unless mongo_id?(object)
|
|
48
|
+
raise ArgumentError, "invalid track ID: #{object.inspect}"
|
|
49
|
+
end
|
|
26
50
|
end
|
|
27
51
|
end
|
|
28
52
|
|
|
29
53
|
def validate_island_id!(object)
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
validate! do
|
|
55
|
+
unless mongo_id?(object)
|
|
56
|
+
raise ArgumentError, "invalid island ID: #{object.inspect}"
|
|
57
|
+
end
|
|
32
58
|
end
|
|
33
59
|
end
|
|
34
60
|
|
|
35
61
|
def validate_country_iso_codes!(iso_codes)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
validate! do
|
|
63
|
+
unless iso_codes.is_a?(Array)
|
|
64
|
+
raise ArgumentError, "country ISO codes should be an array: #{iso_codes.inspect}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
iso_codes.each do |iso_code|
|
|
68
|
+
validate_country_iso_code!(iso_code)
|
|
69
|
+
end
|
|
42
70
|
end
|
|
43
71
|
end
|
|
44
72
|
|
|
45
73
|
def validate_country_iso_code!(object)
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
validate! do
|
|
75
|
+
unless country_iso_code?(object)
|
|
76
|
+
raise ArgumentError, "invalid country ISO code: #{object.inspect}"
|
|
77
|
+
end
|
|
48
78
|
end
|
|
49
79
|
end
|
|
50
80
|
|
|
51
81
|
def validate_decades!(decades)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
validate! do
|
|
83
|
+
unless decades.is_a?(Array)
|
|
84
|
+
raise ArgumentError, "decades should be an array: #{decades.inspect}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
decades.each do |decade|
|
|
88
|
+
validate_decade!(decade)
|
|
89
|
+
end
|
|
58
90
|
end
|
|
59
91
|
end
|
|
60
92
|
|
|
61
93
|
def validate_decade!(object)
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
validate! do
|
|
95
|
+
unless decade?(object)
|
|
96
|
+
raise ArgumentError, "invalid decade: #{object.inspect}"
|
|
97
|
+
end
|
|
64
98
|
end
|
|
65
99
|
end
|
|
66
100
|
|
|
67
101
|
def validate_moods!(moods)
|
|
68
|
-
|
|
69
|
-
|
|
102
|
+
validate! do
|
|
103
|
+
unless moods.is_a?(Array)
|
|
104
|
+
raise ArgumentError, "moods should be an array: #{moods.inspect}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
moods.each do |mood|
|
|
108
|
+
validate_mood!(mood)
|
|
109
|
+
end
|
|
70
110
|
end
|
|
111
|
+
end
|
|
71
112
|
|
|
72
|
-
|
|
73
|
-
|
|
113
|
+
def validate_mood!(object)
|
|
114
|
+
validate! do
|
|
115
|
+
unless mood?(object)
|
|
116
|
+
raise ArgumentError, "invalid mood: #{object.inspect}"
|
|
117
|
+
end
|
|
74
118
|
end
|
|
75
119
|
end
|
|
76
120
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
def validate_user_track_status!(object)
|
|
122
|
+
validate! do
|
|
123
|
+
unless user_track_status?(object)
|
|
124
|
+
raise ArgumentError, "invalid user track status: #{object.inspect}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def validate_page_size!(object)
|
|
130
|
+
validate! do
|
|
131
|
+
unless positive_number?(object)
|
|
132
|
+
raise ArgumentError, "invalid page size: #{object.inspect}"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def validate_page_number!(object)
|
|
138
|
+
validate! do
|
|
139
|
+
unless positive_number?(object)
|
|
140
|
+
raise ArgumentError, "invalid page number: #{object.inspect}"
|
|
141
|
+
end
|
|
80
142
|
end
|
|
81
143
|
end
|
|
82
144
|
end
|
data/lib/radio5/version.rb
CHANGED
data/lib/radio5.rb
CHANGED
|
@@ -22,4 +22,18 @@ module Radio5
|
|
|
22
22
|
}.freeze
|
|
23
23
|
|
|
24
24
|
MOODS = MOODS_MAPPING.keys.freeze
|
|
25
|
+
|
|
26
|
+
USER_TRACK_STATUSES_MAPPING = {
|
|
27
|
+
posted: "posted",
|
|
28
|
+
rejected: "rejected",
|
|
29
|
+
on_air: "onair",
|
|
30
|
+
confirmation: "confirmation",
|
|
31
|
+
duplicate: "duplicate",
|
|
32
|
+
deleted: "deleted",
|
|
33
|
+
broken: "broken"
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
USER_TRACK_STATUSES = USER_TRACK_STATUSES_MAPPING.keys.freeze
|
|
37
|
+
|
|
38
|
+
MAX_PAGE_SIZE = 1_000_000_000
|
|
25
39
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: radio5
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmytro Horoshko
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-01-
|
|
11
|
+
date: 2024-01-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Adapter for Radiooooo private API.
|
|
14
14
|
email:
|