const FileSync = require ( 'lowdb/adapters/FileSync' )
var fs = require ( 'fs-extra' ) ;
const { uuid } = require ( 'uuidv4' ) ;
var path = require ( 'path' ) ;
var youtubedl = require ( 'youtube-dl' ) ;
const config _api = require ( './config' ) ;
const twitch _api = require ( './twitch' ) ;
var utils = require ( './utils' ) ;
const debugMode = process . env . YTDL _MODE === 'debug' ;
var logger = null ;
var db = null ;
var users _db = null ;
var db _api = null ;
function setDB ( input _db , input _users _db , input _db _api ) { db = input _db ; users _db = input _users _db ; db _api = input _db _api }
function setLogger ( input _logger ) { logger = input _logger ; }
function initialize ( input _db , input _users _db , input _logger , input _db _api ) {
setDB ( input _db , input _users _db , input _db _api ) ;
setLogger ( input _logger ) ;
}
async function subscribe ( sub , user _uid = null ) {
const result _obj = {
success : false ,
error : ''
} ;
return new Promise ( async resolve => {
// sub should just have url and name. here we will get isPlaylist and path
sub . isPlaylist = sub . url . includes ( 'playlist' ) ;
sub . videos = [ ] ;
let url _exists = false ;
if ( user _uid )
url _exists = ! ! users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { url : sub . url } ) . value ( )
else
url _exists = ! ! db . get ( 'subscriptions' ) . find ( { url : sub . url } ) . value ( ) ;
if ( ! sub . name && url _exists ) {
logger . error ( ` Sub with the same URL " ${ sub . url } " already exists -- please provide a custom name for this new subscription. ` ) ;
result _obj . error = 'Subcription with URL ' + sub . url + ' already exists! Custom name is required.' ;
resolve ( result _obj ) ;
return ;
}
// add sub to db
let sub _db = null ;
if ( user _uid ) {
users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . push ( sub ) . write ( ) ;
sub _db = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) ;
} else {
db . get ( 'subscriptions' ) . push ( sub ) . write ( ) ;
sub _db = db . get ( 'subscriptions' ) . find ( { id : sub . id } ) ;
}
let success = await getSubscriptionInfo ( sub , user _uid ) ;
if ( success ) {
sub = sub _db . value ( ) ;
getVideosForSub ( sub , user _uid ) ;
} else {
logger . error ( 'Subscribe: Failed to get subscription info. Subscribe failed.' )
} ;
result _obj . success = success ;
result _obj . sub = sub ;
resolve ( result _obj ) ;
} ) ;
}
async function getSubscriptionInfo ( sub , user _uid = null ) {
let basePath = null ;
if ( user _uid )
basePath = path . join ( config _api . getConfigItem ( 'ytdl_users_base_path' ) , user _uid , 'subscriptions' ) ;
else
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
// get videos
let downloadConfig = [ '--dump-json' , '--playlist-end' , '1' ] ;
let useCookies = config _api . getConfigItem ( 'ytdl_use_cookies' ) ;
if ( useCookies ) {
if ( await fs . pathExists ( path . join ( _ _dirname , 'appdata' , 'cookies.txt' ) ) ) {
downloadConfig . push ( '--cookies' , path . join ( 'appdata' , 'cookies.txt' ) ) ;
} else {
logger . warn ( 'Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.' ) ;
}
}
return new Promise ( resolve => {
youtubedl . exec ( sub . url , downloadConfig , { } , function ( err , output ) {
if ( debugMode ) {
logger . info ( 'Subscribe: got info for subscription ' + sub . id ) ;
}
if ( err ) {
logger . error ( err . stderr ) ;
resolve ( false ) ;
} else if ( output ) {
if ( output . length === 0 || ( output . length === 1 && output [ 0 ] === '' ) ) {
logger . verbose ( 'Could not get info for ' + sub . id ) ;
resolve ( false ) ;
}
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
if ( ! output _json ) {
continue ;
}
if ( ! sub . name ) {
if ( sub . isPlaylist ) {
sub . name = output _json . playlist _title ? output _json . playlist _title : output _json . playlist ;
} else {
sub . name = output _json . uploader ;
}
// if it's now valid, update
if ( sub . name ) {
if ( user _uid )
users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( { name : sub . name } ) . write ( ) ;
else
db . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( { name : sub . name } ) . write ( ) ;
}
}
const useArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
if ( useArchive && ! sub . archive ) {
// must create the archive
const archive _dir = path . join ( _ _dirname , basePath , 'archives' , sub . name ) ;
const archive _path = path . join ( archive _dir , 'archive.txt' ) ;
// creates archive directory and text file if it doesn't exist
fs . ensureDirSync ( archive _dir ) ;
fs . ensureFileSync ( archive _path ) ;
// updates subscription
sub . archive = archive _dir ;
if ( user _uid )
users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( { archive : archive _dir } ) . write ( ) ;
else
db . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( { archive : archive _dir } ) . write ( ) ;
}
// TODO: get even more info
resolve ( true ) ;
}
resolve ( false ) ;
}
} ) ;
} ) ;
}
async function unsubscribe ( sub , deleteMode , user _uid = null ) {
let basePath = null ;
if ( user _uid )
basePath = path . join ( config _api . getConfigItem ( 'ytdl_users_base_path' ) , user _uid , 'subscriptions' ) ;
else
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
let result _obj = { success : false , error : '' } ;
let id = sub . id ;
if ( user _uid )
users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . remove ( { id : id } ) . write ( ) ;
else
db . get ( 'subscriptions' ) . remove ( { id : id } ) . write ( ) ;
// failed subs have no name, on unsubscribe they shouldn't error
if ( ! sub . name ) {
return ;
}
const appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
if ( deleteMode && ( await fs . pathExists ( appendedBasePath ) ) ) {
if ( sub . archive && ( await fs . pathExists ( sub . archive ) ) ) {
const archive _file _path = path . join ( sub . archive , 'archive.txt' ) ;
// deletes archive if it exists
if ( await fs . pathExists ( archive _file _path ) ) {
await fs . unlink ( archive _file _path ) ;
}
await fs . rmdir ( sub . archive ) ;
}
await fs . remove ( appendedBasePath ) ;
}
}
async function deleteSubscriptionFile ( sub , file , deleteForever , file _uid = null , user _uid = null ) {
let basePath = null ;
let sub _db = null ;
if ( user _uid ) {
basePath = path . join ( config _api . getConfigItem ( 'ytdl_users_base_path' ) , user _uid , 'subscriptions' ) ;
sub _db = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) ;
} else {
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
sub _db = db . get ( 'subscriptions' ) . find ( { id : sub . id } ) ;
}
const useArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
const appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
const name = file ;
let retrievedID = null ;
sub _db . get ( 'videos' ) . remove ( { uid : file _uid } ) . write ( ) ;
let filePath = appendedBasePath ;
const ext = ( sub . type && sub . type === 'audio' ) ? '.mp3' : '.mp4'
var jsonPath = path . join ( _ _dirname , filePath , name + '.info.json' ) ;
var videoFilePath = path . join ( _ _dirname , filePath , name + ext ) ;
var imageFilePath = path . join ( _ _dirname , filePath , name + '.jpg' ) ;
var altImageFilePath = path . join ( _ _dirname , filePath , name + '.webp' ) ;
const [ jsonExists , videoFileExists , imageFileExists , altImageFileExists ] = await Promise . all ( [
fs . pathExists ( jsonPath ) ,
fs . pathExists ( videoFilePath ) ,
fs . pathExists ( imageFilePath ) ,
fs . pathExists ( altImageFilePath ) ,
] ) ;
if ( jsonExists ) {
retrievedID = JSON . parse ( await fs . readFile ( jsonPath , 'utf8' ) ) [ 'id' ] ;
await fs . unlink ( jsonPath ) ;
}
if ( imageFileExists ) {
await fs . unlink ( imageFilePath ) ;
}
if ( altImageFileExists ) {
await fs . unlink ( altImageFilePath ) ;
}
if ( videoFileExists ) {
await fs . unlink ( videoFilePath ) ;
if ( ( await fs . pathExists ( jsonPath ) ) || ( await fs . pathExists ( videoFilePath ) ) ) {
return false ;
} else {
// check if the user wants the video to be redownloaded (deleteForever === false)
if ( ! deleteForever && useArchive && sub . archive && retrievedID ) {
const archive _path = path . join ( sub . archive , 'archive.txt' )
// if archive exists, remove line with video ID
if ( await fs . pathExists ( archive _path ) ) {
await removeIDFromArchive ( archive _path , retrievedID ) ;
}
}
return true ;
}
} else {
// TODO: tell user that the file didn't exist
return true ;
}
}
async function getVideosForSub ( sub , user _uid = null ) {
// get sub_db
let sub _db = null ;
if ( user _uid )
sub _db = users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) ;
else
sub _db = db . get ( 'subscriptions' ) . find ( { id : sub . id } ) ;
const latest _sub _obj = sub _db . value ( ) ;
if ( ! latest _sub _obj || latest _sub _obj [ 'downloading' ] ) {
return false ;
}
updateSubscriptionProperty ( sub , { downloading : true } , user _uid ) ;
// get basePath
let basePath = null ;
if ( user _uid )
basePath = path . join ( config _api . getConfigItem ( 'ytdl_users_base_path' ) , user _uid , 'subscriptions' ) ;
else
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
let appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
let multiUserMode = null ;
if ( user _uid ) {
multiUserMode = {
user : user _uid ,
file _path : appendedBasePath
}
}
const downloadConfig = await generateArgsForSubscription ( sub , user _uid ) ;
// get videos
logger . verbose ( 'Subscription: getting videos for subscription ' + sub . name ) ;
return new Promise ( resolve => {
youtubedl . exec ( sub . url , downloadConfig , { } , async function ( err , output ) {
updateSubscriptionProperty ( sub , { downloading : false } , user _uid ) ;
logger . verbose ( 'Subscription: finished check for ' + sub . name ) ;
if ( err && ! output ) {
logger . error ( err . stderr ? err . stderr : err . message ) ;
if ( err . stderr . includes ( 'This video is unavailable' ) ) {
logger . info ( 'An error was encountered with at least one video, backup method will be used.' )
try {
const outputs = err . stdout . split ( /\r\n|\r|\n/ ) ;
for ( let i = 0 ; i < outputs . length ; i ++ ) {
const output = JSON . parse ( outputs [ i ] ) ;
handleOutputJSON ( sub , sub _db , output , i === 0 , multiUserMode )
if ( err . stderr . includes ( output [ 'id' ] ) && archive _path ) {
// we found a video that errored! add it to the archive to prevent future errors
if ( sub . archive ) {
archive _dir = sub . archive ;
archive _path = path . join ( archive _dir , 'archive.txt' )
fs . appendFileSync ( archive _path , output [ 'id' ] ) ;
}
}
}
} catch ( e ) {
logger . error ( 'Backup method failed. See error below:' ) ;
logger . error ( e ) ;
}
}
resolve ( false ) ;
} else if ( output ) {
if ( output . length === 0 || ( output . length === 1 && output [ 0 ] === '' ) ) {
logger . verbose ( 'No additional videos to download for ' + sub . name ) ;
resolve ( true ) ;
return ;
}
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
if ( ! output _json ) {
continue ;
}
const reset _videos = i === 0 ;
handleOutputJSON ( sub , sub _db , output _json , multiUserMode , reset _videos ) ;
}
if ( config _api . getConfigItem ( 'ytdl_subscriptions_redownload_fresh_uploads' ) ) {
await setFreshUploads ( sub , user _uid ) ;
checkVideosForFreshUploads ( sub , user _uid ) ;
}
resolve ( true ) ;
}
} ) ;
} , err => {
logger . error ( err ) ;
updateSubscriptionProperty ( sub , { downloading : false } , user _uid ) ;
} ) ;
}
async function generateArgsForSubscription ( sub , user _uid , redownload = false , desired _path = null ) {
// get basePath
let basePath = null ;
if ( user _uid )
basePath = path . join ( config _api . getConfigItem ( 'ytdl_users_base_path' ) , user _uid , 'subscriptions' ) ;
else
basePath = config _api . getConfigItem ( 'ytdl_subscriptions_base_path' ) ;
const useArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
let appendedBasePath = getAppendedBasePath ( sub , basePath ) ;
let fullOutput = ` ${ appendedBasePath } /%(title)s.%(ext)s ` ;
if ( desired _path ) {
fullOutput = ` ${ desired _path } .%(ext)s ` ;
} else if ( sub . custom _output ) {
fullOutput = ` ${ appendedBasePath } / ${ sub . custom _output } .%(ext)s ` ;
}
let downloadConfig = [ '-o' , fullOutput , ! redownload ? '-ciw' : '-ci' , '--write-info-json' , '--print-json' ] ;
let qualityPath = null ;
if ( sub . type && sub . type === 'audio' ) {
qualityPath = [ '-f' , 'bestaudio' ]
qualityPath . push ( '-x' ) ;
qualityPath . push ( '--audio-format' , 'mp3' ) ;
} else {
if ( ! sub . maxQuality || sub . maxQuality === 'best' ) qualityPath = [ '-f' , 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' ] ;
else qualityPath = [ '-f' , ` bestvideo[height<= ${ sub . maxQuality } ]+bestaudio/best[height<= ${ sub . maxQuality } ] ` , '--merge-output-format' , 'mp4' ] ;
}
downloadConfig . push ( ... qualityPath )
if ( sub . custom _args ) {
customArgsArray = sub . custom _args . split ( ',,' ) ;
if ( customArgsArray . indexOf ( '-f' ) !== - 1 ) {
// if custom args has a custom quality, replce the original quality with that of custom args
const original _output _index = downloadConfig . indexOf ( '-f' ) ;
downloadConfig . splice ( original _output _index , 2 ) ;
}
downloadConfig . push ( ... customArgsArray ) ;
}
let archive _dir = null ;
let archive _path = null ;
if ( useArchive && ! redownload ) {
if ( sub . archive ) {
archive _dir = sub . archive ;
archive _path = path . join ( archive _dir , 'archive.txt' )
}
downloadConfig . push ( '--download-archive' , archive _path ) ;
}
// if streaming only mode, just get the list of videos
if ( sub . streamingOnly ) {
downloadConfig = [ '-f' , 'best' , '--dump-json' ] ;
}
if ( sub . timerange && ! redownload ) {
downloadConfig . push ( '--dateafter' , sub . timerange ) ;
}
let useCookies = config _api . getConfigItem ( 'ytdl_use_cookies' ) ;
if ( useCookies ) {
if ( await fs . pathExists ( path . join ( _ _dirname , 'appdata' , 'cookies.txt' ) ) ) {
downloadConfig . push ( '--cookies' , path . join ( 'appdata' , 'cookies.txt' ) ) ;
} else {
logger . warn ( 'Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.' ) ;
}
}
if ( config _api . getConfigItem ( 'ytdl_include_thumbnail' ) ) {
downloadConfig . push ( '--write-thumbnail' ) ;
}
return downloadConfig ;
}
function handleOutputJSON ( sub , sub _db , output _json , multiUserMode = null , reset _videos = false ) {
if ( sub . streamingOnly ) {
if ( reset _videos ) {
sub _db . assign ( { videos : [ ] } ) . write ( ) ;
}
// remove unnecessary info
output _json . formats = null ;
// add to db
sub _db . get ( 'videos' ) . push ( output _json ) . write ( ) ;
} else {
path _object = path . parse ( output _json [ '_filename' ] ) ;
const path _string = path . format ( path _object ) ;
if ( sub _db . get ( 'videos' ) . find ( { path : path _string } ) . value ( ) ) {
// file already exists in DB, return early to avoid reseting the download date
return ;
}
db _api . registerFileDB ( path . basename ( output _json [ '_filename' ] ) , sub . type , multiUserMode , sub ) ;
const url = output _json [ 'webpage_url' ] ;
if ( sub . type === 'video' && url . includes ( 'twitch.tv/videos/' ) && url . split ( 'twitch.tv/videos/' ) . length > 1
&& config _api . getConfigItem ( 'ytdl_use_twitch_api' ) && config _api . getConfigItem ( 'ytdl_twitch_auto_download_chat' ) ) {
const file _name = path . basename ( output _json [ '_filename' ] ) ;
const id = file _name . substring ( 0 , file _name . length - 4 ) ;
let vodId = url . split ( 'twitch.tv/videos/' ) [ 1 ] ;
vodId = vodId . split ( '?' ) [ 0 ] ;
twitch _api . downloadTwitchChatByVODID ( vodId , id , sub . type , multiUserMode . user , sub ) ;
}
}
}
function getSubscriptions ( user _uid = null ) {
if ( user _uid )
return users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . value ( ) ;
else
return db . get ( 'subscriptions' ) . value ( ) ;
}
function getAllSubscriptions ( ) {
let subscriptions = null ;
const multiUserMode = config _api . getConfigItem ( 'ytdl_multi_user_mode' ) ;
if ( multiUserMode ) {
subscriptions = [ ] ;
let users = users _db . get ( 'users' ) . value ( ) ;
for ( let i = 0 ; i < users . length ; i ++ ) {
if ( users [ i ] [ 'subscriptions' ] ) subscriptions = subscriptions . concat ( users [ i ] [ 'subscriptions' ] ) ;
}
} else {
subscriptions = getSubscriptions ( ) ;
}
return subscriptions ;
}
function getSubscription ( subID , user _uid = null ) {
if ( user _uid )
return users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : subID } ) . value ( ) ;
else
return db . get ( 'subscriptions' ) . find ( { id : subID } ) . value ( ) ;
}
function getSubscriptionByName ( subName , user _uid = null ) {
if ( user _uid )
return users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { name : subName } ) . value ( ) ;
else
return db . get ( 'subscriptions' ) . find ( { name : subName } ) . value ( ) ;
}
function updateSubscription ( sub , user _uid = null ) {
if ( user _uid ) {
users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( sub ) . write ( ) ;
} else {
db . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( sub ) . write ( ) ;
}
return true ;
}
function updateSubscriptionPropertyMultiple ( subs , assignment _obj ) {
subs . forEach ( sub => {
updateSubscriptionProperty ( sub , assignment _obj , sub . user _uid ) ;
} ) ;
}
function updateSubscriptionProperty ( sub , assignment _obj , user _uid = null ) {
if ( user _uid ) {
users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( assignment _obj ) . write ( ) ;
} else {
db . get ( 'subscriptions' ) . find ( { id : sub . id } ) . assign ( assignment _obj ) . write ( ) ;
}
return true ;
}
function subExists ( subID , user _uid = null ) {
if ( user _uid )
return ! ! users _db . get ( 'users' ) . find ( { uid : user _uid } ) . get ( 'subscriptions' ) . find ( { id : subID } ) . value ( ) ;
else
return ! ! db . get ( 'subscriptions' ) . find ( { id : subID } ) . value ( ) ;
}
async function setFreshUploads ( sub , user _uid ) {
const current _date = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] . replace ( /-/g , '' ) ;
sub . videos . forEach ( async video => {
if ( current _date === video [ 'upload_date' ] . replace ( /-/g , '' ) ) {
// set upload as fresh
const video _uid = video [ 'uid' ] ;
await db _api . setVideoProperty ( video _uid , { 'fresh_upload' : true } , user _uid , sub [ 'id' ] ) ;
}
} ) ;
}
async function checkVideosForFreshUploads ( sub , user _uid ) {
const current _date = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] . replace ( /-/g , '' ) ;
sub . videos . forEach ( async video => {
if ( video [ 'fresh_upload' ] && current _date > video [ 'upload_date' ] . replace ( /-/g , '' ) ) {
checkVideoIfBetterExists ( video , sub , user _uid )
}
} ) ;
}
async function checkVideoIfBetterExists ( file _obj , sub , user _uid ) {
const new _path = file _obj [ 'path' ] . substring ( 0 , file _obj [ 'path' ] . length - 4 ) ;
const downloadConfig = await generateArgsForSubscription ( sub , user _uid , true , new _path ) ;
logger . verbose ( ` Checking if a better version of the fresh upload ${ file _obj [ 'id' ] } exists. ` ) ;
// simulate a download to verify that a better version exists
youtubedl . getInfo ( file _obj [ 'url' ] , downloadConfig , ( err , output ) => {
if ( err ) {
// video is not available anymore for whatever reason
} else if ( output ) {
const metric _to _compare = sub . type === 'audio' ? 'abr' : 'height' ;
if ( output [ metric _to _compare ] > file _obj [ metric _to _compare ] ) {
// download new video as the simulated one is better
youtubedl . exec ( file _obj [ 'url' ] , downloadConfig , async ( err , output ) => {
if ( err ) {
logger . verbose ( ` Failed to download better version of video ${ file _obj [ 'id' ] } ` ) ;
} else if ( output ) {
logger . verbose ( ` Successfully upgraded video ${ file _obj [ 'id' ] } 's ${ metric _to _compare } from ${ file _obj [ metric _to _compare ] } to ${ output [ metric _to _compare ] } ` ) ;
await db _api . setVideoProperty ( file _obj [ 'uid' ] , { [ metric _to _compare ] : output [ metric _to _compare ] } , user _uid , sub [ 'id' ] ) ;
}
} ) ;
}
}
} ) ;
await db _api . setVideoProperty ( file _obj [ 'uid' ] , { 'fresh_upload' : false } , user _uid , sub [ 'id' ] ) ;
}
// helper functions
function getAppendedBasePath ( sub , base _path ) {
return path . join ( base _path , ( sub . isPlaylist ? 'playlists/' : 'channels/' ) , sub . name ) ;
}
async function removeIDFromArchive ( archive _path , id ) {
let data = await fs . readFile ( archive _path , { encoding : 'utf-8' } ) ;
if ( ! data ) {
logger . error ( 'Archive could not be found.' ) ;
return ;
}
let dataArray = data . split ( '\n' ) ; // convert file data in an array
const searchKeyword = id ; // we are looking for a line, contains, key word id in the file
let lastIndex = - 1 ; // let say, we have not found the keyword
for ( let index = 0 ; index < dataArray . length ; index ++ ) {
if ( dataArray [ index ] . includes ( searchKeyword ) ) { // check if a line contains the id keyword
lastIndex = index ; // found a line includes a id keyword
break ;
}
}
const line = dataArray . splice ( lastIndex , 1 ) ; // remove the keyword id from the data Array
// UPDATE FILE WITH NEW DATA
const updatedData = dataArray . join ( '\n' ) ;
await fs . writeFile ( archive _path , updatedData ) ;
if ( line ) return line ;
if ( err ) throw err ;
}
module . exports = {
getSubscription : getSubscription ,
getSubscriptionByName : getSubscriptionByName ,
getSubscriptions : getSubscriptions ,
getAllSubscriptions : getAllSubscriptions ,
updateSubscription : updateSubscription ,
subscribe : subscribe ,
unsubscribe : unsubscribe ,
deleteSubscriptionFile : deleteSubscriptionFile ,
getVideosForSub : getVideosForSub ,
removeIDFromArchive : removeIDFromArchive ,
setLogger : setLogger ,
initialize : initialize ,
updateSubscriptionPropertyMultiple : updateSubscriptionPropertyMultiple
}