mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 07:42:23 +02:00
YT: store reusable alive info in temp folder
This commit is contained in:
@@ -7,23 +7,37 @@ const { debug } = Debug;
|
|||||||
Gio._promisify(Gio._LocalFilePrototype, 'make_directory_async', 'make_directory_finish');
|
Gio._promisify(Gio._LocalFilePrototype, 'make_directory_async', 'make_directory_finish');
|
||||||
|
|
||||||
function createCacheDirPromise()
|
function createCacheDirPromise()
|
||||||
|
{
|
||||||
|
const dir = Gio.File.new_for_path(
|
||||||
|
GLib.get_user_cache_dir() + '/' + Misc.appId
|
||||||
|
);
|
||||||
|
|
||||||
|
return createDirPromise(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTempDirPromise()
|
||||||
|
{
|
||||||
|
const dir = Gio.File.new_for_path(
|
||||||
|
GLib.get_tmp_dir() + '/.' + Misc.appId
|
||||||
|
);
|
||||||
|
|
||||||
|
return createDirPromise(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDirPromise(dir)
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const cacheDir = Gio.File.new_for_path(
|
if(dir.query_exists(null))
|
||||||
GLib.get_user_cache_dir() + '/' + Misc.appId
|
return resolve(dir);
|
||||||
);
|
|
||||||
|
|
||||||
if(cacheDir.query_exists(null))
|
const dirCreated = await dir.make_directory_async(
|
||||||
return resolve(cacheDir);
|
|
||||||
|
|
||||||
const dirCreated = await cacheDir.make_directory_async(
|
|
||||||
GLib.PRIORITY_DEFAULT,
|
GLib.PRIORITY_DEFAULT,
|
||||||
null,
|
null,
|
||||||
).catch(debug);
|
).catch(debug);
|
||||||
|
|
||||||
if(!dirCreated)
|
if(!dirCreated)
|
||||||
return reject(new Error(`could not create dir: ${cacheDir.get_path()}`));
|
return reject(new Error(`could not create dir: ${dir.get_path()}`));
|
||||||
|
|
||||||
resolve(cacheDir);
|
resolve(dir);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
104
src/youtube.js
104
src/youtube.js
@@ -83,7 +83,34 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
debug(`obtaining YouTube video info: ${videoId}`);
|
debug(`obtaining YouTube video info: ${videoId}`);
|
||||||
this.downloadingVideoId = videoId;
|
this.downloadingVideoId = videoId;
|
||||||
|
|
||||||
let result = await this._getInfoPromise(videoId).catch(debug);
|
let result;
|
||||||
|
let isFoundInTemp = false;
|
||||||
|
|
||||||
|
const tempInfo = await this._getFileContentsPromise('tmp', 'yt-info', videoId).catch(debug);
|
||||||
|
if(tempInfo) {
|
||||||
|
debug('checking temp info for requested video');
|
||||||
|
let parsedTempInfo;
|
||||||
|
|
||||||
|
try { parsedTempInfo = JSON.parse(tempInfo); }
|
||||||
|
catch(err) { debug(err); }
|
||||||
|
|
||||||
|
if(parsedTempInfo) {
|
||||||
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||||
|
const { expireDate } = parsedTempInfo.streamingData;
|
||||||
|
|
||||||
|
if(expireDate && expireDate > nowSeconds) {
|
||||||
|
debug(`found usable info, remaining live: ${expireDate - nowSeconds}`);
|
||||||
|
|
||||||
|
isFoundInTemp = true;
|
||||||
|
result = { data: parsedTempInfo };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
debug('temp info expired');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!result)
|
||||||
|
result = await this._getInfoPromise(videoId).catch(debug);
|
||||||
|
|
||||||
if(!result || !result.data) {
|
if(!result || !result.data) {
|
||||||
if(result && result.isAborted)
|
if(result && result.isAborted)
|
||||||
@@ -153,7 +180,7 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
debug(`found player URI: ${ytUri}`);
|
debug(`found player URI: ${ytUri}`);
|
||||||
|
|
||||||
const ytId = ytPath.split('/').find(el => Misc.isHex(el));
|
const ytId = ytPath.split('/').find(el => Misc.isHex(el));
|
||||||
actions = await this._getCacheFileActionsPromise(ytId).catch(debug);
|
actions = await this._getFileContentsPromise('user_cache', 'yt-sig', ytId).catch(debug);
|
||||||
|
|
||||||
if(!actions) {
|
if(!actions) {
|
||||||
result = await this._downloadDataPromise(ytUri).catch(debug);
|
result = await this._downloadDataPromise(ytUri).catch(debug);
|
||||||
@@ -167,8 +194,8 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
|
|
||||||
actions = YTDL.sig.extractActions(result.data);
|
actions = YTDL.sig.extractActions(result.data);
|
||||||
if(actions) {
|
if(actions) {
|
||||||
debug('deciphered');
|
debug('deciphered, saving cipher actions to cache file');
|
||||||
this._createCacheFileAsync(ytId, actions);
|
this._createSubdirFileAsync('user_cache', 'yt-sig', ytId, actions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!actions || !actions.length) {
|
if(!actions || !actions.length) {
|
||||||
@@ -191,6 +218,19 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!isFoundInTemp) {
|
||||||
|
const exp = info.streamingData.expiresInSeconds || 0;
|
||||||
|
const len = info.videoDetails.lengthSeconds || 3;
|
||||||
|
|
||||||
|
/* Estimated safe time for rewatching video */
|
||||||
|
info.streamingData.expireDate = Math.floor(Date.now() / 1000)
|
||||||
|
+ Number(exp) - (3 * len);
|
||||||
|
|
||||||
|
this._createSubdirFileAsync(
|
||||||
|
'tmp', 'yt-info', videoId, JSON.stringify(info)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.lastInfo = info;
|
this.lastInfo = info;
|
||||||
this.emit('info-resolved', true);
|
this.emit('info-resolved', true);
|
||||||
this.downloadingVideoId = null;
|
this.downloadingVideoId = null;
|
||||||
@@ -330,7 +370,7 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
let info = null;
|
let info = null;
|
||||||
|
|
||||||
try { info = JSON.parse(playerResponse); }
|
try { info = JSON.parse(playerResponse); }
|
||||||
catch(err) { debug(err.message) }
|
catch(err) { debug(err.message); }
|
||||||
|
|
||||||
if(!info)
|
if(!info)
|
||||||
return reject(new Error('could not parse video info JSON'));
|
return reject(new Error('could not parse video info JSON'));
|
||||||
@@ -460,65 +500,53 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
return `${url}&${sig}=${key}`;
|
return `${url}&${sig}=${key}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createCacheFileAsync(ytId, actions)
|
async _createSubdirFileAsync(place, folderName, fileName, data)
|
||||||
{
|
{
|
||||||
debug('saving cipher actions to cache file');
|
const destDir = Gio.File.new_for_path([
|
||||||
|
GLib[`get_${place}_dir`](),
|
||||||
const ytCacheDir = Gio.File.new_for_path([
|
|
||||||
GLib.get_user_cache_dir(),
|
|
||||||
Misc.appId,
|
Misc.appId,
|
||||||
'yt-sig'
|
folderName
|
||||||
].join('/'));
|
].join('/'));
|
||||||
|
|
||||||
for(let dir of [ytCacheDir.get_parent(), ytCacheDir]) {
|
for(let dir of [destDir.get_parent(), destDir]) {
|
||||||
if(dir.query_exists(null))
|
const createdDir = await FileOps.createDirPromise(dir).catch(debug);
|
||||||
continue;
|
if(!createdDir) return;
|
||||||
|
|
||||||
const dirCreated = await dir.make_directory_async(
|
|
||||||
GLib.PRIORITY_DEFAULT,
|
|
||||||
null,
|
|
||||||
).catch(debug);
|
|
||||||
|
|
||||||
if(!dirCreated) {
|
|
||||||
debug(new Error(`could not create dir: ${dir.get_path()}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheFile = ytCacheDir.get_child(ytId);
|
const destFile = destDir.get_child(fileName);
|
||||||
cacheFile.replace_contents_bytes_async(
|
destFile.replace_contents_bytes_async(
|
||||||
GLib.Bytes.new_take(actions),
|
GLib.Bytes.new_take(data),
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
Gio.FileCreateFlags.NONE,
|
Gio.FileCreateFlags.NONE,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
.then(() => debug('saved cache file'))
|
.then(() => debug(`saved file: ${destFile.get_path()}`))
|
||||||
.catch(debug);
|
.catch(debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCacheFileActionsPromise(ytId)
|
_getFileContentsPromise(place, folderName, fileName)
|
||||||
{
|
{
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
debug('checking decipher actions from cache file');
|
debug(`reading data from ${place} file`);
|
||||||
|
|
||||||
const ytActionsFile = Gio.File.new_for_path([
|
const file = Gio.File.new_for_path([
|
||||||
GLib.get_user_cache_dir(),
|
GLib[`get_${place}_dir`](),
|
||||||
Misc.appId,
|
Misc.appId,
|
||||||
'yt-sig',
|
folderName,
|
||||||
ytId
|
fileName
|
||||||
].join('/'));
|
].join('/'));
|
||||||
|
|
||||||
if(!ytActionsFile.query_exists(null)) {
|
if(!file.query_exists(null)) {
|
||||||
debug(`no such cache file: ${ytId}`);
|
debug(`no such file: ${file.get_path()}`);
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ytActionsFile.load_bytes_async(null)
|
file.load_bytes_async(null)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
const data = result[0].get_data();
|
const data = result[0].get_data();
|
||||||
if(!data || !data.length)
|
if(!data || !data.length)
|
||||||
return reject(new Error('actions cache file is empty'));
|
return reject(new Error('source file is empty'));
|
||||||
|
|
||||||
if(data instanceof Uint8Array)
|
if(data instanceof Uint8Array)
|
||||||
resolve(ByteArray.toString(data));
|
resolve(ByteArray.toString(data));
|
||||||
|
Reference in New Issue
Block a user