import { BlogPost } from 'floatplane/creator' ; import { fApi } from './FloatplaneAPI' ; import Channel from './Channel' ; import db from '@inrixia/db' ; import type { SubscriptionSettings } from './types' ; import type Video from './Video' ; type LastSeenVideo = { guid : BlogPost [ 'guid' ] ; releaseDate : BlogPost [ 'releaseDate' ] ; } ; type SubscriptionDB = { lastSeenVideo : LastSeenVideo ; videos : BlogPost [ ] ; } ; export default class Subscription { public channels : Channel [ ] ; public defaultChannel : Channel ; public creatorId : string ; private _db : SubscriptionDB ; constructor ( subscription ) { this . creatorId = subscription . creatorId ; this . channels = Object . values ( subscription . channels ) . map ( ( channel ) => new Channel ( channel , this ) ) ; this . defaultChannel = new Channel ( subscription . channels . _default , this ) ; const databaseFilePath = `./db/subscriptions/${ subscription . creatorId }.json` ; try { this . _db = db < SubscriptionDB > ( databaseFilePath , { template : { lastSeenVideo : { guid : '' , releaseDate : '' } , videos : [ ] } } ) ; } catch { throw new Error ( `Cannot load Subscription database file ${ databaseFilePath }! Please delete the file or fix it!` ) ; } } get lastSeenVideo ( ) : SubscriptionDB [ 'lastSeenVideo' ] { return this . _db . lastSeenVideo ; } public updateLastSeenVideo ( videoSeen ) { if ( this . lastSeenVideo . releaseDate === '' || new Date ( videoSeen . releaseDate ) > new Date ( this . lastSeenVideo . releaseDate ) ) this . _db . lastSeenVideo = videoSeen ; } param { fApiVideo } video public addVideo ( video , overrideSkip , stripSubchannelPrefix ? ) ; public addVideo ( video , overrideSkip ? , stripSubchannelPrefix ? ) ; public addVideo ( video , overrideSkip = false , stripSubchannelPrefix = true ) { for ( const channel of this . channels ) { if ( channel . identifiers === false ) continue ; for ( const identifier of channel . identifiers ) { if ( typeof identifier . type !== 'string' ) throw new Error ( `Video value for channel identifier type ${ video [ identifier . type ] } on channel ${ channel . title } is of type ${ typeof video [ identifier . type ] } not string!` ) ; else { const identifierType = identifier . type === 'description' ? 'text' : identifier . type ; if ( ( video [ identifierType ] as string ) . toLowerCase ( ) . indexOf ( identifier . check . toLowerCase ( ) ) !== - 1 ) { if ( overrideSkip === false && channel . skip === true ) return null ; const idCheck = identifier . check . replace ( /[-\/\\^$*+?.()|[\]{}]/g , '\\$&' ) ; const regIDCheck = new RegExp ( idCheck , 'i' ) ; if ( identifierType === 'title' && stripSubchannelPrefix === true ) video . title = video . title . replace ( regIDCheck , '' ) . trim ( ) ; return channel . addVideo ( video ) ; } } } } if ( overrideSkip === false && this . defaultChannel . skip === true ) return null ; return this . defaultChannel . addVideo ( video ) ; } public async fetchNewVideos ( videosToSearch = 20 , stripSubchannelPrefix , ignoreBeforeTimestamp ) { const coloredTitle = `${ this . defaultChannel . consoleColor || '\u001b[38;5;208m' }${ this . defaultChannel . title }\u001b[0m` ; const videos = [ ] ; process . stdout . write ( `> Fetching latest videos from [${ coloredTitle }]... Fetched ${ videos . length } videos!` ) ; for await ( const video of fApi . creator . blogPostsIterable ( this . creatorId , { type : 'video' } ) ) { if ( video . guid === this . lastSeenVideo . guid ) { if ( ! ( await this . addVideo ( video , true , stripSubchannelPrefix ) . isDownloaded ( ) ) ) this . lastSeenVideo . guid = '' ; else break ; } if ( this . lastSeenVideo . guid === '' && videos . length >= videosToSearch ) break ; videos . push ( video ) ; process . stdout . write ( `\r> Fetching latest videos from [${ coloredTitle }]... Fetched ${ videos . length } videos!` ) ; } const incompleteVideos = [ ] ; for ( const video of videos . sort ( ( a , b ) => + new Date ( a . releaseDate ) - + new Date ( b . releaseDate ) ) . map ( ( video ) => this . addVideo ( video , false , stripSubchannelPrefix ) ) ) { if ( video === null || ( await video . isMuxed ( ) ) ) continue ; if ( ignoreBeforeTimestamp !== false && new Date ( video . releaseDate ) . getTime ( ) < ignoreBeforeTimestamp ) continue ; incompleteVideos . push ( video ) ; } process . stdout . write ( ` Skipped ${ videos . length - incompleteVideos . length }.\n` ) ; return incompleteVideos ; } }