" I model the API for the [Invidious](https://invidious.io/) open source front-end for YouTube. " Class { #name : #Invidious, #superclass : #Object, #instVars : [ 'uri', 'region', 'api', 'monitor' ], #classInstVars : [ 'default' ], #category : #VideoWeb } { #category : #accessing } Invidious class >> default: anObject [ default := anObject ] { #category : #accessing } Invidious class >> defaultInstance [ ^ default ifNil: [ default := self responseTimes first key ] ] { #category : #accessing } Invidious class >> instances [ ^ self rawInstances collect: [ :instanceData | | instanceDict | instanceDict := instanceData second. Invidious new fromDictionary: instanceDict ] ] { #category : #accessing } Invidious class >> instancesWithAPIEnabled [ ^ self instances select: [ :instance | instance api = true ] ] { #category : #accessing } Invidious class >> rawInstances [ ^ STON fromString: 'https://api.invidious.io/instances.json?sort_by=type,users' asUrl retrieveContents ] { #category : #accessing } Invidious class >> responseTimes [ | responses | responses := Dictionary new. self instancesWithAPIEnabled do: [ :instance | responses at: instance put: instance responseTime. ]. ^ (responses associations asSortedCollection: [ :x :y| x value < y value ]) collect: [:assoc | assoc ] ] { #category : #accessing } Invidious class >> storeConfiguration [ | configFolder | configFolder := (FileLocator userData / 'Mutabit' / 'VideoWeb') ensureCreateDirectory. MarkupFile exportAsFileOn: configFolder / 'config.ston' containing: ( STON toStringPretty: self defaultInstance ). ^ configFolder / 'config.ston' ] { #category : #accessing } Invidious >> api [ ^ api ] { #category : #accessing } Invidious >> api: booleanValue [ api := booleanValue. ] { #category : #accessing } Invidious >> authorIdForVideo: aString [ ] { #category : #accessing } Invidious >> channel: authorId [ | queryURL | queryURL := self uri asString, 'api/v1/channels/', authorId . ^ STONJSON fromString: queryURL asUrl retrieveContents ] { #category : #accessing } Invidious >> channelContainingVideo: videoID [ ^ self channel: (self video: videoID) authorId ] { #category : #accessing } Invidious >> fromDictionary: aDictionary [ | rawMonitor| self uri: (aDictionary at: 'uri'); region: (aDictionary at: 'region'); api: (aDictionary at: 'api'). rawMonitor := aDictionary at: 'monitor'. rawMonitor ifNil: [ ^ self ]. self monitor at: '90DaysRatio' put: ((aDictionary at: 'monitor' at: '90dRatio') at: 'ratio'); at: '30DaysRatio' put: ((aDictionary at: 'monitor' at: '30dRatio') at: 'ratio'); yourself. ] { #category : #accessing } Invidious >> monitor [ ^ monitor ifNil: [ monitor := Dictionary new] ] { #category : #accessing } Invidious >> monitor: aDictionary [ monitor := aDictionary ] { #category : #accessing } Invidious >> printOn: aStream [ | ratio | ratio := self monitor at: '90DaysRatio' ifAbsent: [ '--' ]. super printOn: aStream. ^ aStream nextPutAll: ' ( ', self uri asString, ' | ', self region, ' | ', ratio, '% ) ' ] { #category : #accessing } Invidious >> region [ ^ region ] { #category : #accessing } Invidious >> region: countryISOCode [ region := countryISOCode ] { #category : #accessing } Invidious >> responseTime [ ^ [ ZnClient new beOneShot; url: self uri; get; response ] timeToRun ] { #category : #accessing } Invidious >> uri [ ^ uri ] { #category : #accessing } Invidious >> uri: urlString [ uri := urlString asUrl ] { #category : #accessing } Invidious >> video: videoId [ | dataUrl queryFields dataDict | queryFields := 'videoId,title,description,author,authorId,authorUrl,likeCount,lengthSeconds,videoThumbnails'. dataUrl := (self uri asString, 'api/v1/videos/', videoId, '?fields=', queryFields, '&pretty=1') asUrl. dataDict := STONJSON fromString: dataUrl retrieveContents. ^ WebVideo new fromDictionary: dataDict ]