1
1
use async_graphql:: Result ;
2
2
use database:: { MediaLot , MediaSource } ;
3
+ use enum_meta:: HashMap ;
4
+ use itertools:: Itertools ;
5
+ use sea_orm:: prelude:: DateTimeUtc ;
3
6
use serde:: { Deserialize , Serialize } ;
4
- use serde_json:: { json, Value } ;
7
+ use serde_json:: json;
5
8
use surf:: {
6
9
http:: headers:: { ACCEPT , USER_AGENT } ,
7
10
Client , Config , Url ,
@@ -11,7 +14,9 @@ use crate::{
11
14
importer:: {
12
15
DeployUrlAndKeyAndUsernameImportInput , ImportFailStep , ImportFailedItem , ImportResult ,
13
16
} ,
14
- models:: media:: { ImportOrExportItemIdentifier , ImportOrExportMediaItem } ,
17
+ models:: media:: {
18
+ ImportOrExportItemIdentifier , ImportOrExportMediaItem , ImportOrExportMediaItemSeen ,
19
+ } ,
15
20
utils:: USER_AGENT_STR ,
16
21
} ;
17
22
@@ -28,6 +33,7 @@ enum CollectionType {
28
33
enum MediaType {
29
34
Movie ,
30
35
Series ,
36
+ Episode ,
31
37
#[ serde( untagged) ]
32
38
Unknown ( String ) ,
33
39
}
@@ -38,13 +44,26 @@ struct ItemProviderIdsPayload {
38
44
tmdb : Option < String > ,
39
45
}
40
46
47
+ #[ derive( Serialize , Deserialize , Debug , Clone ) ]
48
+ #[ serde( rename_all = "PascalCase" ) ]
49
+ struct ItemUserData {
50
+ play_count : Option < i32 > ,
51
+ last_played_date : Option < DateTimeUtc > ,
52
+ is_favorite : Option < bool > ,
53
+ }
54
+
41
55
#[ derive( Serialize , Deserialize , Debug , Clone ) ]
42
56
#[ serde( rename_all = "PascalCase" ) ]
43
57
struct ItemResponse {
44
58
id : String ,
45
59
name : String ,
46
60
#[ serde( rename = "Type" ) ]
47
61
typ : Option < MediaType > ,
62
+ index_number : Option < i32 > ,
63
+ series_id : Option < String > ,
64
+ series_name : Option < String > ,
65
+ user_data : Option < ItemUserData > ,
66
+ parent_index_number : Option < i32 > ,
48
67
collection_type : Option < CollectionType > ,
49
68
provider_ids : Option < ItemProviderIdsPayload > ,
50
69
}
@@ -55,32 +74,41 @@ struct ItemsResponse {
55
74
items : Vec < ItemResponse > ,
56
75
}
57
76
77
+ #[ derive( Serialize , Deserialize , Debug , Clone ) ]
78
+ #[ serde( rename_all = "PascalCase" ) ]
79
+ struct AuthenticateResponse {
80
+ user : ItemResponse ,
81
+ access_token : String ,
82
+ }
83
+
58
84
pub async fn import ( input : DeployUrlAndKeyAndUsernameImportInput ) -> Result < ImportResult > {
59
- let mut media = vec ! [ ] ;
60
- let mut failed_items = vec ! [ ] ;
85
+ let authenticate: AuthenticateResponse = surf:: post ( format ! (
86
+ "{}/Users/AuthenticateByName" ,
87
+ input. api_url
88
+ ) )
89
+ . header (
90
+ "X-Emby-Authorization" ,
91
+ r#"MediaBrowser , Client="other", Device="script", DeviceId="script", Version="0.0.0""# ,
92
+ )
93
+ . body_json ( & serde_json:: json!( { "Username" : input. username, "Pw" : input. password } ) )
94
+ . unwrap ( )
95
+ . await ?
96
+ . body_json ( )
97
+ . await ?;
61
98
let client: Client = Config :: new ( )
62
99
. add_header ( USER_AGENT , USER_AGENT_STR )
63
100
. unwrap ( )
64
101
. add_header ( ACCEPT , "application/json" )
65
102
. unwrap ( )
66
- . add_header ( "X-Emby-Token" , input . api_key )
103
+ . add_header ( "X-Emby-Token" , authenticate . access_token )
67
104
. unwrap ( )
68
105
. set_base_url ( Url :: parse ( & input. api_url ) . unwrap ( ) . join ( "/" ) . unwrap ( ) )
69
106
. try_into ( )
70
107
. unwrap ( ) ;
108
+ let user_id = authenticate. user . id ;
71
109
72
- let users_data: Vec < ItemResponse > = client
73
- . get ( "Users" )
74
- . await
75
- . unwrap ( )
76
- . body_json ( )
77
- . await
78
- . unwrap ( ) ;
79
- let user_id = users_data
80
- . into_iter ( )
81
- . find ( |x| x. name == input. username )
82
- . unwrap ( )
83
- . id ;
110
+ let mut to_handle_media = vec ! [ ] ;
111
+ let mut failed_items = vec ! [ ] ;
84
112
85
113
let views_data: ItemsResponse = client
86
114
. get ( & format ! ( "Users/{}/Views" , user_id) )
@@ -89,6 +117,9 @@ pub async fn import(input: DeployUrlAndKeyAndUsernameImportInput) -> Result<Impo
89
117
. body_json ( )
90
118
. await
91
119
. unwrap ( ) ;
120
+
121
+ let mut series_id_to_tmdb_id: HashMap < String , Option < String > > = HashMap :: new ( ) ;
122
+
92
123
for library in views_data. items {
93
124
let collection_type = library. collection_type . unwrap ( ) ;
94
125
if matches ! ( collection_type, CollectionType :: Unknown ( _) ) {
@@ -100,13 +131,10 @@ pub async fn import(input: DeployUrlAndKeyAndUsernameImportInput) -> Result<Impo
100
131
} ) ;
101
132
continue ;
102
133
}
103
- let mut query = json ! ( {
134
+ let query = json ! ( {
104
135
"parentId" : library. id, "recursive" : true ,
105
- "includeItemTypes " : "Movie,Series" , "fields" : "ProviderIds"
136
+ "IsPlayed " : true , "fields" : "ProviderIds"
106
137
} ) ;
107
- if collection_type == CollectionType :: Movies {
108
- query[ "filters" ] = Value :: String ( "IsPlayed" . to_string ( ) ) ;
109
- }
110
138
let library_data: ItemsResponse = client
111
139
. get ( & format ! ( "Users/{}/Items" , user_id) )
112
140
. query ( & query)
@@ -117,22 +145,34 @@ pub async fn import(input: DeployUrlAndKeyAndUsernameImportInput) -> Result<Impo
117
145
. await
118
146
. unwrap ( ) ;
119
147
for item in library_data. items {
120
- let typ = item. typ . unwrap ( ) ;
121
- match typ. clone ( ) {
122
- MediaType :: Movie => {
123
- let tmdb_id = item. provider_ids . unwrap ( ) . tmdb . unwrap ( ) ;
124
- media. push ( ImportOrExportMediaItem {
125
- source_id : item. name ,
126
- lot : MediaLot :: Movie ,
127
- source : MediaSource :: Tmdb ,
128
- internal_identifier : Some ( ImportOrExportItemIdentifier :: NeedsDetails (
148
+ let typ = item. typ . clone ( ) . unwrap ( ) ;
149
+ tracing:: debug!( "Processing item: {:?} ({:?})" , item. name, typ) ;
150
+ let ( lot, tmdb_id, ssn, sen) = match typ. clone ( ) {
151
+ MediaType :: Movie => ( MediaLot :: Movie , item. provider_ids . unwrap ( ) . tmdb , None , None ) ,
152
+ MediaType :: Series | MediaType :: Episode => {
153
+ if let Some ( series_id) = item. series_id {
154
+ let mut tmdb_id = series_id_to_tmdb_id. get ( & series_id) . cloned ( ) . flatten ( ) ;
155
+ if tmdb_id. is_none ( ) {
156
+ let details: ItemResponse = client
157
+ . get ( & format ! ( "Items/{}" , series_id) )
158
+ . await
159
+ . unwrap ( )
160
+ . body_json ( )
161
+ . await
162
+ . unwrap ( ) ;
163
+ let insert_id = details. provider_ids . unwrap ( ) . tmdb ;
164
+ series_id_to_tmdb_id. insert ( series_id. clone ( ) , insert_id. clone ( ) ) ;
165
+ tmdb_id = insert_id;
166
+ }
167
+ (
168
+ MediaLot :: Show ,
129
169
tmdb_id,
130
- ) ) ,
131
- identifier : "" . to_string ( ) ,
132
- seen_history : vec ! [ ] ,
133
- reviews : vec ! [ ] ,
134
- collections : vec ! [ ] ,
135
- } ) ;
170
+ item . parent_index_number ,
171
+ item . index_number ,
172
+ )
173
+ } else {
174
+ continue ;
175
+ }
136
176
}
137
177
_ => {
138
178
failed_items. push ( ImportFailedItem {
@@ -143,10 +183,64 @@ pub async fn import(input: DeployUrlAndKeyAndUsernameImportInput) -> Result<Impo
143
183
} ) ;
144
184
continue ;
145
185
}
186
+ } ;
187
+ if let Some ( tmdb_id) = tmdb_id {
188
+ let item_user_data = item. user_data . unwrap ( ) ;
189
+ let num_times_seen = item_user_data. play_count . unwrap_or ( 0 ) ;
190
+ let mut seen_history = ( 0 ..num_times_seen)
191
+ . map ( |_| ImportOrExportMediaItemSeen {
192
+ show_season_number : ssn,
193
+ show_episode_number : sen,
194
+ ..Default :: default ( )
195
+ } )
196
+ . collect_vec ( ) ;
197
+ if let Some ( last) = seen_history. last_mut ( ) {
198
+ last. ended_on = item_user_data. last_played_date ;
199
+ } ;
200
+ let mut collections = vec ! [ ] ;
201
+ if let Some ( true ) = item_user_data. is_favorite {
202
+ collections. push ( "Favorites" . to_string ( ) ) ;
203
+ }
204
+ to_handle_media. push ( ImportOrExportMediaItem {
205
+ lot,
206
+ source_id : item. series_name . unwrap_or ( item. name ) ,
207
+ source : MediaSource :: Tmdb ,
208
+ internal_identifier : Some ( ImportOrExportItemIdentifier :: NeedsDetails (
209
+ tmdb_id. clone ( ) ,
210
+ ) ) ,
211
+ seen_history,
212
+ identifier : tmdb_id,
213
+ reviews : vec ! [ ] ,
214
+ collections,
215
+ } ) ;
216
+ } else {
217
+ failed_items. push ( ImportFailedItem {
218
+ step : ImportFailStep :: ItemDetailsFromSource ,
219
+ identifier : item. name ,
220
+ error : Some ( "No tmdb id found" . to_string ( ) ) ,
221
+ lot : None ,
222
+ } ) ;
146
223
}
147
224
}
148
225
}
149
226
227
+ let mut media: Vec < ImportOrExportMediaItem > = vec ! [ ] ;
228
+
229
+ for item in to_handle_media {
230
+ let mut found = false ;
231
+ for media_item in media. iter_mut ( ) {
232
+ if media_item. identifier == item. identifier && media_item. lot == item. lot {
233
+ found = true ;
234
+ media_item. seen_history . extend ( item. seen_history . clone ( ) ) ;
235
+ media_item. collections . extend ( item. collections . clone ( ) ) ;
236
+ break ;
237
+ }
238
+ }
239
+ if !found {
240
+ media. push ( item) ;
241
+ }
242
+ }
243
+
150
244
Ok ( ImportResult {
151
245
media,
152
246
failed_items,
0 commit comments