tangled
alpha
login
or
join now
tsiry-sandratraina.com
/
tunein-cli
6
fork
atom
Browse and listen to thousands of radio stations across the globe right from your terminal ๐ ๐ป ๐ตโจ
radio
rust
tokio
web-radio
command-line-tool
tui
6
fork
atom
overview
issues
pulls
pipelines
implement `browse` api
tsiry-sandratraina.com
3 years ago
14958c2c
39831e32
+208
-27
7 changed files
expand all
collapse all
unified
split
proto
objects
v1alpha1
category.proto
station.proto
tunein
v1alpha1
browse.proto
src
api
objects.v1alpha1.rs
tunein.v1alpha1.rs
lib.rs
server
browse.rs
+5
-1
proto/objects/v1alpha1/category.proto
···
2
2
3
3
package objects.v1alpha1;
4
4
5
5
+
import "objects/v1alpha1/station.proto";
6
6
+
5
7
message Category {
6
6
-
8
8
+
string id = 1;
9
9
+
string name = 2;
10
10
+
repeated objects.v1alpha1.Station stations = 3;
7
11
}
+19
-1
proto/objects/v1alpha1/station.proto
···
3
3
package objects.v1alpha1;
4
4
5
5
message Station {
6
6
-
6
6
+
string id = 1;
7
7
+
string name = 2;
8
8
+
string playing = 3;
9
9
+
}
10
10
+
11
11
+
message StationLinkDetails {
12
12
+
uint32 bitrate = 1;
13
13
+
string element = 2;
14
14
+
string is_ad_clipped_content_enabled = 3;
15
15
+
bool is_direct = 4;
16
16
+
string is_hls_advanced = 5;
17
17
+
string live_seek_stream = 6;
18
18
+
string media_type = 7;
19
19
+
uint32 player_height = 8;
20
20
+
uint32 player_width = 9;
21
21
+
string playlist_type = 10;
22
22
+
uint32 position = 11;
23
23
+
uint32 reliability = 12;
24
24
+
string url = 13;
7
25
}
+12
-3
proto/tunein/v1alpha1/browse.proto
···
2
2
3
3
package tunein.v1alpha1;
4
4
5
5
+
import "objects/v1alpha1/category.proto";
6
6
+
import "objects/v1alpha1/station.proto";
7
7
+
5
8
message GetCategoriesRequest {}
6
9
7
7
-
message GetCategoriesResponse {}
10
10
+
message GetCategoriesResponse {
11
11
+
repeated objects.v1alpha1.Category categories = 1;
12
12
+
}
8
13
9
14
message BrowseCategoryRequest {
10
15
string category_id = 1;
11
16
}
12
17
13
13
-
message BrowseCategoryResponse {}
18
18
+
message BrowseCategoryResponse {
19
19
+
repeated objects.v1alpha1.Category categories = 1;
20
20
+
}
14
21
15
22
message GetStationDetailsRequest {
16
23
string id = 1;
17
24
}
18
25
19
19
-
message GetStationDetailsResponse {}
26
26
+
message GetStationDetailsResponse {
27
27
+
repeated objects.v1alpha1.StationLinkDetails station_link_details = 1;
28
28
+
}
20
29
21
30
service BrowseService {
22
31
rpc GetCategories(GetCategoriesRequest) returns (GetCategoriesResponse) {}
+46
-2
src/api/objects.v1alpha1.rs
···
1
1
#[allow(clippy::derive_partial_eq_without_eq)]
2
2
#[derive(Clone, PartialEq, ::prost::Message)]
3
3
-
pub struct Category {}
3
3
+
pub struct Station {
4
4
+
#[prost(string, tag = "1")]
5
5
+
pub id: ::prost::alloc::string::String,
6
6
+
#[prost(string, tag = "2")]
7
7
+
pub name: ::prost::alloc::string::String,
8
8
+
#[prost(string, tag = "3")]
9
9
+
pub playing: ::prost::alloc::string::String,
10
10
+
}
4
11
#[allow(clippy::derive_partial_eq_without_eq)]
5
12
#[derive(Clone, PartialEq, ::prost::Message)]
6
6
-
pub struct Station {}
13
13
+
pub struct StationLinkDetails {
14
14
+
#[prost(uint32, tag = "1")]
15
15
+
pub bitrate: u32,
16
16
+
#[prost(string, tag = "2")]
17
17
+
pub element: ::prost::alloc::string::String,
18
18
+
#[prost(string, tag = "3")]
19
19
+
pub is_ad_clipped_content_enabled: ::prost::alloc::string::String,
20
20
+
#[prost(bool, tag = "4")]
21
21
+
pub is_direct: bool,
22
22
+
#[prost(string, tag = "5")]
23
23
+
pub is_hls_advanced: ::prost::alloc::string::String,
24
24
+
#[prost(string, tag = "6")]
25
25
+
pub live_seek_stream: ::prost::alloc::string::String,
26
26
+
#[prost(string, tag = "7")]
27
27
+
pub media_type: ::prost::alloc::string::String,
28
28
+
#[prost(uint32, tag = "8")]
29
29
+
pub player_height: u32,
30
30
+
#[prost(uint32, tag = "9")]
31
31
+
pub player_width: u32,
32
32
+
#[prost(string, tag = "10")]
33
33
+
pub playlist_type: ::prost::alloc::string::String,
34
34
+
#[prost(uint32, tag = "11")]
35
35
+
pub position: u32,
36
36
+
#[prost(uint32, tag = "12")]
37
37
+
pub reliability: u32,
38
38
+
#[prost(string, tag = "13")]
39
39
+
pub url: ::prost::alloc::string::String,
40
40
+
}
41
41
+
#[allow(clippy::derive_partial_eq_without_eq)]
42
42
+
#[derive(Clone, PartialEq, ::prost::Message)]
43
43
+
pub struct Category {
44
44
+
#[prost(string, tag = "1")]
45
45
+
pub id: ::prost::alloc::string::String,
46
46
+
#[prost(string, tag = "2")]
47
47
+
pub name: ::prost::alloc::string::String,
48
48
+
#[prost(message, repeated, tag = "3")]
49
49
+
pub stations: ::prost::alloc::vec::Vec<Station>,
50
50
+
}
+14
-3
src/api/tunein.v1alpha1.rs
···
3
3
pub struct GetCategoriesRequest {}
4
4
#[allow(clippy::derive_partial_eq_without_eq)]
5
5
#[derive(Clone, PartialEq, ::prost::Message)]
6
6
-
pub struct GetCategoriesResponse {}
6
6
+
pub struct GetCategoriesResponse {
7
7
+
#[prost(message, repeated, tag = "1")]
8
8
+
pub categories: ::prost::alloc::vec::Vec<super::super::objects::v1alpha1::Category>,
9
9
+
}
7
10
#[allow(clippy::derive_partial_eq_without_eq)]
8
11
#[derive(Clone, PartialEq, ::prost::Message)]
9
12
pub struct BrowseCategoryRequest {
···
12
15
}
13
16
#[allow(clippy::derive_partial_eq_without_eq)]
14
17
#[derive(Clone, PartialEq, ::prost::Message)]
15
15
-
pub struct BrowseCategoryResponse {}
18
18
+
pub struct BrowseCategoryResponse {
19
19
+
#[prost(message, repeated, tag = "1")]
20
20
+
pub categories: ::prost::alloc::vec::Vec<super::super::objects::v1alpha1::Category>,
21
21
+
}
16
22
#[allow(clippy::derive_partial_eq_without_eq)]
17
23
#[derive(Clone, PartialEq, ::prost::Message)]
18
24
pub struct GetStationDetailsRequest {
···
21
27
}
22
28
#[allow(clippy::derive_partial_eq_without_eq)]
23
29
#[derive(Clone, PartialEq, ::prost::Message)]
24
24
-
pub struct GetStationDetailsResponse {}
30
30
+
pub struct GetStationDetailsResponse {
31
31
+
#[prost(message, repeated, tag = "1")]
32
32
+
pub station_link_details: ::prost::alloc::vec::Vec<
33
33
+
super::super::objects::v1alpha1::StationLinkDetails,
34
34
+
>,
35
35
+
}
25
36
/// Generated client implementations.
26
37
pub mod browse_service_client {
27
38
#![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)]
+65
src/lib.rs
···
1
1
pub mod api {
2
2
#[path = ""]
3
3
pub mod tunein {
4
4
+
use tunein::types::CategoryDetails;
5
5
+
6
6
+
use super::objects::v1alpha1::{Category, Station, StationLinkDetails};
7
7
+
4
8
#[path = "tunein.v1alpha1.rs"]
5
9
pub mod v1alpha1;
10
10
+
impl From<CategoryDetails> for Category {
11
11
+
fn from(category: CategoryDetails) -> Self {
12
12
+
Self {
13
13
+
id: category.guide_id.unwrap_or_default(),
14
14
+
name: category.text,
15
15
+
stations: category
16
16
+
.children
17
17
+
.map(|c| {
18
18
+
c.into_iter()
19
19
+
.map(|x| Station {
20
20
+
id: x.guide_id.unwrap_or_default(),
21
21
+
name: x.text,
22
22
+
playing: x.playing.unwrap_or_default(),
23
23
+
})
24
24
+
.collect()
25
25
+
})
26
26
+
.unwrap_or(vec![]),
27
27
+
}
28
28
+
}
29
29
+
}
30
30
+
31
31
+
impl From<tunein::types::Station> for Category {
32
32
+
fn from(s: tunein::types::Station) -> Self {
33
33
+
Self {
34
34
+
id: s.guide_id.unwrap_or_default(),
35
35
+
name: s.text,
36
36
+
stations: s
37
37
+
.children
38
38
+
.map(|c| {
39
39
+
c.into_iter()
40
40
+
.map(|x| Station {
41
41
+
id: x.guide_id.unwrap_or_default(),
42
42
+
name: x.text,
43
43
+
playing: x.playing.unwrap_or_default(),
44
44
+
})
45
45
+
.collect()
46
46
+
})
47
47
+
.unwrap_or(vec![]),
48
48
+
}
49
49
+
}
50
50
+
}
51
51
+
52
52
+
impl From<tunein::types::StationLinkDetails> for StationLinkDetails {
53
53
+
fn from(s: tunein::types::StationLinkDetails) -> Self {
54
54
+
Self {
55
55
+
bitrate: s.bitrate,
56
56
+
element: s.element,
57
57
+
is_ad_clipped_content_enabled: s.is_ad_clipped_content_enabled,
58
58
+
is_direct: s.is_direct,
59
59
+
is_hls_advanced: s.is_hls_advanced,
60
60
+
live_seek_stream: s.live_seek_stream,
61
61
+
media_type: s.media_type,
62
62
+
player_height: s.player_height,
63
63
+
player_width: s.player_width,
64
64
+
playlist_type: s.playlist_type.unwrap_or_default(),
65
65
+
position: s.position,
66
66
+
reliability: s.reliability,
67
67
+
url: s.url,
68
68
+
}
69
69
+
}
70
70
+
}
6
71
}
7
72
8
73
#[path = ""]
+47
-17
src/server/browse.rs
···
1
1
use std::str::FromStr;
2
2
3
3
-
use tunein::{types::Category, TuneInClient};
4
4
-
use tunein_cli::api::tunein::v1alpha1::{
5
5
-
browse_service_server::BrowseService, BrowseCategoryRequest, BrowseCategoryResponse,
6
6
-
GetCategoriesRequest, GetCategoriesResponse, GetStationDetailsRequest,
7
7
-
GetStationDetailsResponse,
3
3
+
use tunein::{
4
4
+
types,
5
5
+
TuneInClient,
6
6
+
};
7
7
+
use tunein_cli::api::{
8
8
+
objects::v1alpha1::{Category, StationLinkDetails},
9
9
+
tunein::v1alpha1::{
10
10
+
browse_service_server::BrowseService, BrowseCategoryRequest, BrowseCategoryResponse,
11
11
+
GetCategoriesRequest, GetCategoriesResponse, GetStationDetailsRequest,
12
12
+
GetStationDetailsResponse,
13
13
+
},
8
14
};
9
15
10
16
pub struct Browse {
···
23
29
impl BrowseService for Browse {
24
30
async fn get_categories(
25
31
&self,
26
26
-
request: tonic::Request<GetCategoriesRequest>,
32
32
+
_request: tonic::Request<GetCategoriesRequest>,
27
33
) -> Result<tonic::Response<GetCategoriesResponse>, tonic::Status> {
28
28
-
self.client
34
34
+
let result = self
35
35
+
.client
29
36
.browse(None)
30
37
.await
31
38
.map_err(|e| tonic::Status::internal(e.to_string()))?;
32
32
-
Ok(tonic::Response::new(GetCategoriesResponse {}))
39
39
+
40
40
+
Ok(tonic::Response::new(GetCategoriesResponse {
41
41
+
categories: result.into_iter().map(Category::from).collect(),
42
42
+
}))
33
43
}
34
44
35
45
async fn browse_category(
···
38
48
) -> Result<tonic::Response<BrowseCategoryResponse>, tonic::Status> {
39
49
let req = request.into_inner();
40
50
let category_id = req.category_id;
41
41
-
let results = match Category::from_str(&category_id) {
42
42
-
Ok(category) => self
43
43
-
.client
44
44
-
.browse(Some(category))
45
45
-
.await
46
46
-
.map_err(|e| tonic::Status::internal(e.to_string()))?,
47
47
-
Err(e) => return Err(tonic::Status::internal(e.to_string())),
51
51
+
let categories: Vec<Category> = match types::Category::from_str(&category_id) {
52
52
+
Ok(category) => {
53
53
+
let results = self
54
54
+
.client
55
55
+
.browse(Some(category))
56
56
+
.await
57
57
+
.map_err(|e| tonic::Status::internal(e.to_string()))?;
58
58
+
results.into_iter().map(Category::from).collect()
59
59
+
}
60
60
+
Err(_) => {
61
61
+
let results = self
62
62
+
.client
63
63
+
.browse_by_id(&category_id)
64
64
+
.await
65
65
+
.map_err(|e| tonic::Status::internal(e.to_string()))?;
66
66
+
results.into_iter().map(Category::from).collect()
67
67
+
}
48
68
};
49
49
-
Ok(tonic::Response::new(BrowseCategoryResponse {}))
69
69
+
Ok(tonic::Response::new(BrowseCategoryResponse { categories }))
50
70
}
51
71
52
72
async fn get_station_details(
53
73
&self,
54
74
request: tonic::Request<GetStationDetailsRequest>,
55
75
) -> Result<tonic::Response<GetStationDetailsResponse>, tonic::Status> {
56
56
-
Ok(tonic::Response::new(GetStationDetailsResponse {}))
76
76
+
let req = request.into_inner();
77
77
+
let station_id = req.id;
78
78
+
let station = self
79
79
+
.client
80
80
+
.get_station(&station_id)
81
81
+
.await
82
82
+
.map_err(|e| tonic::Status::internal(e.to_string()))?;
83
83
+
84
84
+
Ok(tonic::Response::new(GetStationDetailsResponse {
85
85
+
station_link_details: station.into_iter().map(StationLinkDetails::from).collect(),
86
86
+
}))
57
87
}
58
88
}