@@ -44,6 +44,7 @@ public class YoutubeStreamInfoItemLockupExtractor implements StreamInfoItemExtra
4444 private String cachedName ;
4545 private Optional <String > cachedTextualUploadDate ;
4646
47+ private ChannelImageViewModel cachedChannelImageViewModel ;
4748 private JsonArray cachedMetadataRows ;
4849
4950 /**
@@ -165,10 +166,15 @@ public String getUploaderName() throws ParsingException {
165166
166167 @ Override
167168 public String getUploaderUrl () throws ParsingException {
168- final String channelId = JsonUtils .getString (lockupViewModel ,
169- "metadata.lockupMetadataViewModel.image.decoratedAvatarViewModel"
170- + ".rendererContext.commandContext.onTap"
171- + ".innertubeCommand.browseEndpoint.browseId" );
169+ final String channelId = channelImageViewModel ()
170+ .forUploaderUrlExtraction ()
171+ .getObject ("rendererContext" )
172+ .getObject ("commandContext" )
173+ .getObject ("onTap" )
174+ .getObject ("innertubeCommand" )
175+ .getObject ("browseEndpoint" )
176+ .getString ("browseId" );
177+
172178 if (isNullOrEmpty (channelId )) {
173179 throw new ParsingException ("Could not get uploader url" );
174180 }
@@ -179,9 +185,9 @@ public String getUploaderUrl() throws ParsingException {
179185 @ Override
180186 public List <Image > getUploaderAvatars () throws ParsingException {
181187 return YoutubeParsingHelper .getImagesFromThumbnailsArray (
182- JsonUtils .getArray (lockupViewModel ,
183- "metadata.lockupMetadataViewModel.image.decoratedAvatarViewModel"
184- + ".avatar. avatarViewModel.image.sources" ));
188+ JsonUtils .getArray (
189+ channelImageViewModel (). forAvatarExtraction (),
190+ " avatarViewModel.image.sources" ));
185191 }
186192
187193 @ Override
@@ -253,6 +259,34 @@ public List<Image> getThumbnails() throws ParsingException {
253259 "contentImage.thumbnailViewModel.image.sources" ));
254260 }
255261
262+ private ChannelImageViewModel channelImageViewModel () throws ParsingException {
263+ if (cachedChannelImageViewModel == null ) {
264+ cachedChannelImageViewModel = determineChannelImageViewModel ();
265+ }
266+
267+ return cachedChannelImageViewModel ;
268+ }
269+
270+ private ChannelImageViewModel determineChannelImageViewModel () throws ParsingException {
271+ final JsonObject image = lockupViewModel
272+ .getObject ("metadata" )
273+ .getObject ("lockupMetadataViewModel" )
274+ .getObject ("image" );
275+
276+ final JsonObject single = image
277+ .getObject ("decoratedAvatarViewModel" , null );
278+ if (single != null ) {
279+ return new SingleChannelImageViewModel (single );
280+ }
281+
282+ final JsonObject multi = image .getObject ("avatarStackViewModel" , null );
283+ if (multi != null ) {
284+ return new MultiChannelImageViewModel (multi );
285+ }
286+
287+ throw new ParsingException ("Failed to determine channel image view model" );
288+ }
289+
256290 private Optional <JsonObject > metadataPart (final int rowIndex , final int partIndex )
257291 throws ParsingException {
258292 if (cachedMetadataRows == null ) {
@@ -274,4 +308,64 @@ private Optional<JsonObject> metadataPart(final int rowIndex, final int partInde
274308 private String getTextContentFromMetadataPart (final JsonObject metadataPart ) {
275309 return metadataPart .getObject ("text" ).getString ("content" );
276310 }
311+
312+ abstract static class ChannelImageViewModel {
313+ protected JsonObject viewModel ;
314+
315+ protected ChannelImageViewModel (final JsonObject viewModel ) {
316+ this .viewModel = viewModel ;
317+ }
318+
319+ public abstract JsonObject forUploaderUrlExtraction ();
320+
321+ public abstract JsonObject forAvatarExtraction ();
322+ }
323+
324+ static class SingleChannelImageViewModel extends ChannelImageViewModel {
325+ SingleChannelImageViewModel (final JsonObject viewModel ) {
326+ super (viewModel );
327+ }
328+
329+ @ Override
330+ public JsonObject forUploaderUrlExtraction () {
331+ return viewModel ;
332+ }
333+
334+ @ Override
335+ public JsonObject forAvatarExtraction () {
336+ return viewModel .getObject ("avatar" );
337+ }
338+ }
339+
340+ static class MultiChannelImageViewModel extends ChannelImageViewModel {
341+ MultiChannelImageViewModel (final JsonObject viewModel ) {
342+ super (viewModel );
343+ }
344+
345+ @ Override
346+ public JsonObject forUploaderUrlExtraction () {
347+ return viewModel
348+ .getObject ("rendererContext" )
349+ .getObject ("commandContext" )
350+ .getObject ("onTap" )
351+ .getObject ("innertubeCommand" )
352+ .getObject ("showDialogCommand" )
353+ .getObject ("panelLoadingStrategy" )
354+ .getObject ("inlineContent" )
355+ .getObject ("dialogViewModel" )
356+ .getObject ("customContent" )
357+ .getObject ("listViewModel" )
358+ .getArray ("listItems" )
359+ .streamAsJsonObjects ()
360+ .map (item -> item .getObject ("listItemViewModel" ))
361+ .findFirst ()
362+ .orElse (null );
363+ }
364+
365+ @ Override
366+ public JsonObject forAvatarExtraction () {
367+ return viewModel .getArray ("avatars" )
368+ .getObject (0 );
369+ }
370+ }
277371}
0 commit comments