From 912a7b14a00c4eeda19e276d72bd9c6e8ae3f1d7 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:32:31 +0300 Subject: [PATCH 01/21] feat: api type assertion --- src/api/api.js | 123 ++++++++++++++++++++++++++++++++++++++++++- src/utils/helpers.js | 17 +++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/api/api.js b/src/api/api.js index d637b2f..e70c7ca 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,8 +1,12 @@ import parser from './parser.js'; +import { assertType, assertInstance } from '../utils/helpers.js'; import { windowGlobalData } from '../windowGlobalData.js'; export default class Api { async makeApiRequest(rpcid, requestData) { + // type assertion + if (rpcid) assertType(rpcid, 'string'); + requestData = [[[rpcid, JSON.stringify(requestData), null, 'generic']]]; const requestDataString = `f.req=${encodeURIComponent(JSON.stringify(requestData))}&at=${encodeURIComponent(windowGlobalData.at)}&`; @@ -42,6 +46,12 @@ export default class Api { } async getItemsByTakenDate(timestamp = null, source = null, pageId = null, parseResponse = true) { + // type assertion + if (timestamp) assertType(timestamp, 'number'); + if (source) assertType(source, 'string'); + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + // Retrieves media items created before the provided timestamp if (source === 'library') source = 1; else if (source === 'archive') source = 2; @@ -61,6 +71,10 @@ export default class Api { } async getItemsByUploadedDate(pageId = null, parseResponse = true) { + // type assertion + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'EzkLib'; const requestData = ['', [[4, 'ra', 0, 0]], pageId]; try { @@ -74,6 +88,11 @@ export default class Api { } async search(searchQuery, pageId = null, parseResponse = true) { + // type assertion + if (searchQuery) assertType(searchQuery, 'string'); + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'EzkLib'; const requestData = [searchQuery, null, pageId]; try { @@ -87,6 +106,10 @@ export default class Api { } async getFavoriteItems(pageId = null, parseResponse = true) { + // type assertion + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'EzkLib'; const requestData = ['Favorites', [[5, '8', 0, 9]], pageId]; try { @@ -100,6 +123,10 @@ export default class Api { } async getTrashItems(pageId = null, parseResponse = true) { + // type assertion + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'zy0IHe'; const requestData = [pageId]; try { @@ -113,6 +140,10 @@ export default class Api { } async getLockedFolderItems(pageId = null, parseResponse = true) { + // type assertion + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'nMFwOc'; const requestData = [pageId]; try { @@ -126,6 +157,9 @@ export default class Api { } async moveItemsToTrash(mediaIdList) { + // type assertion + if (mediaIdList) assertInstance(mediaIdList, Array); + const rpcid = 'XwAOJf'; const requestData = [null, 1, mediaIdList, 3]; // note: It seems that '3' here corresponds to items' location @@ -139,6 +173,9 @@ export default class Api { } async restoreFromTrash(mediaIdList) { + // type assertion + if (mediaIdList) assertInstance(mediaIdList, Array); + const rpcid = 'XwAOJf'; const requestData = [null, 3, mediaIdList, 2]; try { @@ -151,6 +188,10 @@ export default class Api { } async getSharedLinks(pageId = null, parseResponse = true) { + // type assertion + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'F2A0H'; const requestData = [pageId, null, 2, null, 3]; try { @@ -164,6 +205,10 @@ export default class Api { } async getAlbums(pageId = null, parseResponse = true) { + // type assertion + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'Z5xsfc'; const requestData = [pageId, null, null, null, 1, null, null, 100]; try { @@ -177,7 +222,13 @@ export default class Api { } async getAlbumItems(albumProductId, pageId = null, parseResponse = true) { - // list items of an album or a shared link with the given id + // get items of an album or a shared link with the given id + + // type assertion + if (albumProductId) assertType(albumProductId, 'string'); + if (pageId) assertType(pageId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'snAcKc'; const requestData = [albumProductId, pageId, null, null, 1]; try { @@ -192,6 +243,10 @@ export default class Api { async removeItemsFromAlbum(itemAlbumProductIdList) { // regular productId's won't cut it, you need to get them from an album + + // type assertion + if (itemAlbumProductIdList) assertInstance(itemAlbumProductIdList, Array); + const rpcid = 'ycV3Nd'; const requestData = [itemAlbumProductIdList]; try { @@ -205,6 +260,10 @@ export default class Api { async createAlbum(albumName) { // returns string id of the created album + + // type assertion + if (albumName) assertType(albumName, 'string'); + const rpcid = 'OXvT9d'; let requestData = [albumName, null, 2]; try { @@ -218,6 +277,12 @@ export default class Api { async addItemsToAlbum(productIdList, albumId = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one + + // type assertion + if (productIdList) assertInstance(productIdList, Array); + if (albumId) assertType(albumId, 'string'); + if (albumName) assertType(albumName, 'string'); + const rpcid = 'E1Cajb'; let requestData = null; @@ -235,6 +300,12 @@ export default class Api { async addItemsToSharedAlbum(productIdList, albumId = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one + + // type assertion + if (productIdList) assertInstance(productIdList, Array); + if (albumId) assertType(albumId, 'string'); + if (albumName) assertType(albumName, 'string'); + const rpcid = 'laUYf'; let requestData = null; @@ -251,6 +322,10 @@ export default class Api { } async setFavorite(mediaIdList, action = true) { + // type assertion + if (mediaIdList) assertInstance(mediaIdList, Array); + if (action) assertType(action, 'boolean'); + if (action === true) action = 1; //set favorite else if (action === false) action = 2; //un favorite mediaIdList = mediaIdList.map((item) => [null, item]); @@ -266,6 +341,10 @@ export default class Api { } async setArchive(mediaIdList, action = true) { + // type assertion + if (mediaIdList) assertInstance(mediaIdList, Array); + if (action) assertType(action, 'boolean'); + if (action === true) action = 1; // send to archive else if (action === false) action = 2; // un archive @@ -282,6 +361,9 @@ export default class Api { } async moveToLockedFolder(mediaIdList) { + // type assertion + if (mediaIdList) assertInstance(mediaIdList, Array); + const rpcid = 'StLnCe'; const requestData = [mediaIdList, []]; try { @@ -294,6 +376,9 @@ export default class Api { } async removeFromLockedFolder(mediaIdList) { + // type assertion + if (mediaIdList) assertInstance(mediaIdList, Array); + const rpcid = 'Pp2Xxe'; const requestData = [mediaIdList]; try { @@ -306,6 +391,10 @@ export default class Api { } async removeItemsFromSharedAlbum(albumProductId, itemProductIdList) { + // type assertion + if (albumProductId) assertType(albumProductId, 'string'); + if (itemProductIdList) assertInstance(itemProductIdList, Array); + const rpcid = 'LjmOue'; const requestData = [ [albumProductId], @@ -322,6 +411,14 @@ export default class Api { } async setItemGeoData(mediaId, center, visible1, visible2, scale, gMapsPlaceId) { + // type assertion + if (mediaId) assertType(mediaId, 'string'); + if (center) assertInstance(center, Array); + if (visible1) assertInstance(visible1, Array); + if (visible2) assertInstance(visible2, Array); + if (scale) assertInstance(scale, 'number'); + if (gMapsPlaceId) assertInstance(gMapsPlaceId, 'string'); + // every point is an array of coordinates, every coordinate is 9 digit-long int // coordinates and scale can be extracted from mapThumb, but gMapsPlaceId is not exposed in GP const rpcid = 'EtUHOe'; @@ -336,6 +433,9 @@ export default class Api { } async deleteItemGeoData(mediaId) { + // type assertion + if (mediaId) assertType(mediaId, 'string'); + const rpcid = 'EtUHOe'; const requestData = [[[null, mediaId]], [1]]; try { @@ -350,6 +450,11 @@ export default class Api { async setItemTimestamp(mediaId, timestamp, timezone) { // timestamp in epoch miliseconds // timesone as an offset e.g 19800 is GMT+05:30 + + // type assertion + if (mediaId) assertType(mediaId, 'string'); + if (timestamp) assertType(timestamp, 'number'); + if (timezone) assertType(timezone, 'number'); const rpcid = 'DaSgWe'; const requestData = [[[mediaId, timestamp, timezone]]]; try { @@ -362,6 +467,10 @@ export default class Api { } async setItemDescription(mediaId, description) { + // type assertion + if (mediaId) assertType(mediaId, 'string'); + if (description) assertType(description, 'string'); + const rpcid = 'AQNOFd'; const requestData = [null, description, mediaId]; try { @@ -374,6 +483,10 @@ export default class Api { } async getItemInfo(productId, parseResponse = true) { + // type assertion + if (productId) assertType(productId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'VrseUb'; const requestData = [productId, null, null, 1]; try { @@ -387,6 +500,10 @@ export default class Api { } async getItemInfoExt(productId, parseResponse = true) { + // type assertion + if (productId) assertType(productId, 'string'); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'fDcn4b'; const requestData = [productId, 1]; try { @@ -400,6 +517,10 @@ export default class Api { } async getBatchMediaInfo(productIdList, parseResponse = true) { + // type assertion + if (productIdList) assertInstance(productIdList, Array); + if (parseResponse) assertType(parseResponse, 'boolean'); + const rpcid = 'EWgK9e'; productIdList = productIdList.map((id) => [id]); // prettier-ignore diff --git a/src/utils/helpers.js b/src/utils/helpers.js index a97db68..b66bde4 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -19,4 +19,19 @@ export function isPatternValid(pattern) { } catch (e) { return e; } -} \ No newline at end of file +} +export function assertType(variable, expectedType) { + const actualType = typeof variable; + + if (actualType !== expectedType) { + throw new TypeError(`Expected type ${expectedType} but got ${actualType}`); + } +} + +export function assertInstance(variable, expectedClass) { + const actualClass = variable.constructor.name; + + if (!(variable instanceof expectedClass)) { + throw new TypeError(`Expected instance of ${expectedClass.name} but got ${actualClass}`); + } +} From 9bbd1bde7e11a29746f06871f5f4e7cc2765e7c2 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:33:24 +0300 Subject: [PATCH 02/21] feat: parse movedToTrashTimestamp in itemInfoParse --- src/api/parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/parser.js b/src/api/parser.js index 0b4de24..b63dd5f 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -224,6 +224,7 @@ export default function parser(data, rpcid) { isLivePhoto: rawItemData[0]?.[15]?.[146008172] ? true : false, livePhotoDuration: rawItemData[0]?.[15]?.[146008172]?.[1], livePhotoVideoDownloadUrl: rawItemData[0]?.[15]?.[146008172]?.[3], + movedToTrashTimestamp: rawItemData[0]?.[15]?.[225032867]?.[0], descriptionFull: rawItemData[10], thumb: rawItemData[12], }; From 349c5e28897ce415e4a360a6fe5659b44e47f610 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 04:00:37 +0300 Subject: [PATCH 03/21] chore: parsed properties name changes this commit changes mediaId -> dedupKey productId -> mediaKey timestampTimezone -> timezoneOffset movedToTrashTimestamp -> trashTimestamp name -> title to bring names closer to names used in the official android app's `gphotos0.db` --- README.md | 2 +- src/api/api-utils.js | 48 +++++----- src/api/api.js | 128 ++++++++++++++------------- src/api/parser.js | 48 +++++----- src/gptk-core.js | 6 +- src/ui/logic/action-bar.js | 4 +- src/ui/logic/album-selects-update.js | 2 +- 7 files changed, 122 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index a6bed27..30b4f38 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ In your browser, utilizing GP's undocumented web api // getting the fist page of the library by taken date const libraryPage = await gptkApi.getItemsByTakenDate() // getting the info of the first item on the page - const itemInfo = await gptkApi.getItemInfo(libraryPage.items[0].productId) + const itemInfo = await gptkApi.getItemInfo(libraryPage.items[0].mediaKey) console.log(itemInfo) ``` diff --git a/src/api/api-utils.js b/src/api/api-utils.js index 07f606a..cd5a919 100644 --- a/src/api/api-utils.js +++ b/src/api/api-utils.js @@ -78,11 +78,11 @@ export default class ApiUtils { } async getAllMediaInSharedLink(sharedLinkId) { - return await this.getAllItems(this.api.getAlbumItems, sharedLinkId); + return await this.getAllItems(this.api.getAlbumPage, sharedLinkId); } async getAllMediaInAlbum(albumId) { - return await this.getAllItems(this.api.getAlbumItems, albumId); + return await this.getAllItems(this.api.getAlbumPage, albumId); } async getAllTrashItems() { @@ -104,53 +104,53 @@ export default class ApiUtils { async moveToLockedFolder(mediaItems) { log(`Moving ${mediaItems.length} items to locked folder`); const isSuccess = (result) => Array.isArray(result); - const mediaIdList = mediaItems.map((item) => item.mediaId); - await this.executeWithConcurrency(this.api.moveToLockedFolder, isSuccess, this.lockedFolderOpSize, mediaIdList); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.moveToLockedFolder, isSuccess, this.lockedFolderOpSize, dedupKeyList); } async removeFromLockedFolder(mediaItems) { log(`Moving ${mediaItems.length} items out of locked folder`); const isSuccess = (result) => Array.isArray(result); - const mediaIdList = mediaItems.map((item) => item.mediaId); - await this.executeWithConcurrency(this.api.removeFromLockedFolder, isSuccess, this.lockedFolderOpSize, mediaIdList); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.removeFromLockedFolder, isSuccess, this.lockedFolderOpSize, dedupKeyList); } async moveToTrash(mediaItems) { log(`Moving ${mediaItems.length} items to trash`); const isSuccess = (result) => Array.isArray(result); - const mediaIdList = mediaItems.map((item) => item.mediaId); - await this.executeWithConcurrency(this.api.moveItemsToTrash, isSuccess, this.operationSize, mediaIdList); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.moveItemsToTrash, isSuccess, this.operationSize, dedupKeyList); } async restoreFromTrash(trashItems) { log(`Restoring ${trashItems.length} items from trash`); const isSuccess = (result) => Array.isArray(result); - const mediaIdList = trashItems.map((item) => item.mediaId); - await this.executeWithConcurrency(this.api.restoreFromTrash, isSuccess, this.operationSize, mediaIdList); + const dedupKeyList = trashItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.restoreFromTrash, isSuccess, this.operationSize, dedupKeyList); } async sendToArchive(mediaItems) { log(`Sending ${mediaItems.length} items to archive`); const isSuccess = (result) => Array.isArray(result); mediaItems = mediaItems.filter((item) => item?.isArchived !== true); - const mediaIdList = mediaItems.map((item) => item.mediaId); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); if (!mediaItems) { log('All target items are already archived!'); return; } - await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, mediaIdList, true); + await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, dedupKeyList, true); } async unArchive(mediaItems) { log(`Removing ${mediaItems.length} items from archive`); const isSuccess = (result) => Array.isArray(result); mediaItems = mediaItems.filter((item) => item?.isArchived !== false); - const mediaIdList = mediaItems.map((item) => item.mediaId); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); if (!mediaItems) { log('All target items are not archived!'); return; } - await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, mediaIdList, false); + await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, dedupKeyList, false); } async setAsFavorite(mediaItems) { @@ -161,8 +161,8 @@ export default class ApiUtils { log('All target items are already favorite!'); return; } - const mediaIdList = mediaItems.map((item) => item.mediaId); - await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, mediaIdList, true); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, dedupKeyList, true); } async unFavorite(mediaItems) { @@ -173,15 +173,15 @@ export default class ApiUtils { log('All target items are not favorite!'); return; } - const mediaIdList = mediaItems.map((item) => item.mediaId); - await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, mediaIdList, false); + const dedupKeyList = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, dedupKeyList, false); } async addToExistingAlbum(mediaItems, targetAlbum) { log(`Adding ${mediaItems.length} items to album "${targetAlbum.name}"`); const isSuccess = (result) => Array.isArray(result); - const productIdList = mediaItems.map((item) => item.productId); + const mediaKeyList = mediaItems.map((item) => item.mediaKey); const addItemFunction = targetAlbum.isShared ? this.api.addItemsToSharedAlbum : this.api.addItemsToAlbum; @@ -189,8 +189,8 @@ export default class ApiUtils { addItemFunction, isSuccess, this.operationSize, - productIdList, - targetAlbum.productId + mediaKeyList, + targetAlbum.mediaKey ); } @@ -199,18 +199,18 @@ export default class ApiUtils { const album = {}; album.name = targetAlbumName; album.shared = false; - album.productId = await this.api.createAlbum(targetAlbumName); + album.mediaKey = await this.api.createAlbum(targetAlbumName); await this.addToExistingAlbum(mediaItems, album); } async getBatchMediaInfoChunked(mediaItems) { log('Getting items\' media info'); - const productIdList = mediaItems.map((item) => item.productId); + const mediaKeyList = mediaItems.map((item) => item.mediaKey); const mediaInfoData = await this.executeWithConcurrency( this.api.getBatchMediaInfo, null, this.infoSize, - productIdList + mediaKeyList ); return mediaInfoData; } diff --git a/src/api/api.js b/src/api/api.js index e70c7ca..404d315 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -156,12 +156,12 @@ export default class Api { } } - async moveItemsToTrash(mediaIdList) { + async moveItemsToTrash(dedupKeyList) { // type assertion - if (mediaIdList) assertInstance(mediaIdList, Array); + if (dedupKeyList) assertInstance(dedupKeyList, Array); const rpcid = 'XwAOJf'; - const requestData = [null, 1, mediaIdList, 3]; + const requestData = [null, 1, dedupKeyList, 3]; // note: It seems that '3' here corresponds to items' location try { const response = await this.makeApiRequest(rpcid, requestData); @@ -172,12 +172,12 @@ export default class Api { } } - async restoreFromTrash(mediaIdList) { + async restoreFromTrash(dedupKeyList) { // type assertion - if (mediaIdList) assertInstance(mediaIdList, Array); + if (dedupKeyList) assertInstance(dedupKeyList, Array); const rpcid = 'XwAOJf'; - const requestData = [null, 3, mediaIdList, 2]; + const requestData = [null, 3, dedupKeyList, 2]; try { const response = await this.makeApiRequest(rpcid, requestData); return response[0]; @@ -221,34 +221,40 @@ export default class Api { } } - async getAlbumItems(albumProductId, pageId = null, parseResponse = true) { + async getAlbumPage(albumMediaKey, pageId = null, authKey=null, parseResponse = true) { // get items of an album or a shared link with the given id // type assertion - if (albumProductId) assertType(albumProductId, 'string'); + if (albumMediaKey) assertType(albumMediaKey, 'string'); if (pageId) assertType(pageId, 'string'); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'snAcKc'; - const requestData = [albumProductId, pageId, null, null, 1]; + let requestData = null; + if (authKey){ + requestData = [albumMediaKey, pageId, null, null, 1]; + }else{ + requestData = [albumMediaKey,pageId,null,authKey]; + } + try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); return response; } catch (error) { - console.error('Error in getAlbumItems:', error); + console.error('Error in getAlbumPage:', error); throw error; } } - async removeItemsFromAlbum(itemAlbumProductIdList) { - // regular productId's won't cut it, you need to get them from an album + async removeItemsFromAlbum(itemalbumMediaKeyList) { + // regular mediaKey's won't cut it, you need to get them from an album // type assertion - if (itemAlbumProductIdList) assertInstance(itemAlbumProductIdList, Array); + if (itemalbumMediaKeyList) assertInstance(itemalbumMediaKeyList, Array); const rpcid = 'ycV3Nd'; - const requestData = [itemAlbumProductIdList]; + const requestData = [itemalbumMediaKeyList]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -275,19 +281,19 @@ export default class Api { } } - async addItemsToAlbum(productIdList, albumId = null, albumName = null) { + async addItemsToAlbum(mediaKeyList, albumId = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one // type assertion - if (productIdList) assertInstance(productIdList, Array); + if (mediaKeyList) assertInstance(mediaKeyList, Array); if (albumId) assertType(albumId, 'string'); if (albumName) assertType(albumName, 'string'); const rpcid = 'E1Cajb'; let requestData = null; - if (albumName) requestData = [productIdList, null, albumName]; - else if (albumId) requestData = [productIdList, albumId]; + if (albumName) requestData = [mediaKeyList, null, albumName]; + else if (albumId) requestData = [mediaKeyList, albumId]; try { const response = await this.makeApiRequest(rpcid, requestData); @@ -298,19 +304,19 @@ export default class Api { } } - async addItemsToSharedAlbum(productIdList, albumId = null, albumName = null) { + async addItemsToSharedAlbum(mediaKeyList, albumId = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one // type assertion - if (productIdList) assertInstance(productIdList, Array); + if (mediaKeyList) assertInstance(mediaKeyList, Array); if (albumId) assertType(albumId, 'string'); if (albumName) assertType(albumName, 'string'); const rpcid = 'laUYf'; let requestData = null; - if (albumName) requestData = [productIdList, null, albumName]; - else if (albumId) requestData = [albumId, [2, null, productIdList.map((id) => [[id]]), null, null, null, [1]]]; + if (albumName) requestData = [mediaKeyList, null, albumName]; + else if (albumId) requestData = [albumId, [2, null, mediaKeyList.map((id) => [[id]]), null, null, null, [1]]]; try { const response = await this.makeApiRequest(rpcid, requestData); @@ -321,16 +327,16 @@ export default class Api { } } - async setFavorite(mediaIdList, action = true) { + async setFavorite(dedupKeyList, action = true) { // type assertion - if (mediaIdList) assertInstance(mediaIdList, Array); + if (dedupKeyList) assertInstance(dedupKeyList, Array); if (action) assertType(action, 'boolean'); if (action === true) action = 1; //set favorite else if (action === false) action = 2; //un favorite - mediaIdList = mediaIdList.map((item) => [null, item]); + dedupKeyList = dedupKeyList.map((item) => [null, item]); const rpcid = 'Ftfh0'; - const requestData = [mediaIdList, [action]]; + const requestData = [dedupKeyList, [action]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -340,17 +346,17 @@ export default class Api { } } - async setArchive(mediaIdList, action = true) { + async setArchive(dedupKeyList, action = true) { // type assertion - if (mediaIdList) assertInstance(mediaIdList, Array); + if (dedupKeyList) assertInstance(dedupKeyList, Array); if (action) assertType(action, 'boolean'); if (action === true) action = 1; // send to archive else if (action === false) action = 2; // un archive - mediaIdList = mediaIdList.map((item) => [null, [action], [null, item]]); + dedupKeyList = dedupKeyList.map((item) => [null, [action], [null, item]]); const rpcid = 'w7TP3c'; - const requestData = [mediaIdList, null, 1]; + const requestData = [dedupKeyList, null, 1]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -360,12 +366,12 @@ export default class Api { } } - async moveToLockedFolder(mediaIdList) { + async moveToLockedFolder(dedupKeyList) { // type assertion - if (mediaIdList) assertInstance(mediaIdList, Array); + if (dedupKeyList) assertInstance(dedupKeyList, Array); const rpcid = 'StLnCe'; - const requestData = [mediaIdList, []]; + const requestData = [dedupKeyList, []]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -375,12 +381,12 @@ export default class Api { } } - async removeFromLockedFolder(mediaIdList) { + async removeFromLockedFolder(dedupKeyList) { // type assertion - if (mediaIdList) assertInstance(mediaIdList, Array); + if (dedupKeyList) assertInstance(dedupKeyList, Array); const rpcid = 'Pp2Xxe'; - const requestData = [mediaIdList]; + const requestData = [dedupKeyList]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -390,14 +396,14 @@ export default class Api { } } - async removeItemsFromSharedAlbum(albumProductId, itemProductIdList) { + async removeItemsFromSharedAlbum(albumMediaKey, itemProductIdList) { // type assertion - if (albumProductId) assertType(albumProductId, 'string'); + if (albumMediaKey) assertType(albumMediaKey, 'string'); if (itemProductIdList) assertInstance(itemProductIdList, Array); const rpcid = 'LjmOue'; const requestData = [ - [albumProductId], + [albumMediaKey], [itemProductIdList], [[null, null, null, [null, [], []], null, null, null, null, null, null, null, null, null, []]], ]; @@ -410,9 +416,9 @@ export default class Api { } } - async setItemGeoData(mediaId, center, visible1, visible2, scale, gMapsPlaceId) { + async setItemGeoData(dedupKey, center, visible1, visible2, scale, gMapsPlaceId) { // type assertion - if (mediaId) assertType(mediaId, 'string'); + if (dedupKey) assertType(dedupKey, 'string'); if (center) assertInstance(center, Array); if (visible1) assertInstance(visible1, Array); if (visible2) assertInstance(visible2, Array); @@ -422,7 +428,7 @@ export default class Api { // every point is an array of coordinates, every coordinate is 9 digit-long int // coordinates and scale can be extracted from mapThumb, but gMapsPlaceId is not exposed in GP const rpcid = 'EtUHOe'; - const requestData = [[[null, mediaId]], [2, center, [visible1, visible2], [null, null, scale], gMapsPlaceId]]; + const requestData = [[[null, dedupKey]], [2, center, [visible1, visible2], [null, null, scale], gMapsPlaceId]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -432,12 +438,12 @@ export default class Api { } } - async deleteItemGeoData(mediaId) { + async deleteItemGeoData(dedupKey) { // type assertion - if (mediaId) assertType(mediaId, 'string'); + if (dedupKey) assertType(dedupKey, 'string'); const rpcid = 'EtUHOe'; - const requestData = [[[null, mediaId]], [1]]; + const requestData = [[[null, dedupKey]], [1]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -447,16 +453,16 @@ export default class Api { } } - async setItemTimestamp(mediaId, timestamp, timezone) { + async setItemTimestamp(dedupKey, timestamp, timezone) { // timestamp in epoch miliseconds // timesone as an offset e.g 19800 is GMT+05:30 // type assertion - if (mediaId) assertType(mediaId, 'string'); + if (dedupKey) assertType(dedupKey, 'string'); if (timestamp) assertType(timestamp, 'number'); if (timezone) assertType(timezone, 'number'); const rpcid = 'DaSgWe'; - const requestData = [[[mediaId, timestamp, timezone]]]; + const requestData = [[[dedupKey, timestamp, timezone]]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -466,13 +472,13 @@ export default class Api { } } - async setItemDescription(mediaId, description) { + async setItemDescription(dedupKey, description) { // type assertion - if (mediaId) assertType(mediaId, 'string'); + if (dedupKey) assertType(dedupKey, 'string'); if (description) assertType(description, 'string'); const rpcid = 'AQNOFd'; - const requestData = [null, description, mediaId]; + const requestData = [null, description, dedupKey]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -482,13 +488,13 @@ export default class Api { } } - async getItemInfo(productId, parseResponse = true) { + async getItemInfo(mediaKey, parseResponse = true) { // type assertion - if (productId) assertType(productId, 'string'); + if (mediaKey) assertType(mediaKey, 'string'); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'VrseUb'; - const requestData = [productId, null, null, 1]; + const requestData = [mediaKey, null, null, 1]; try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); @@ -499,13 +505,13 @@ export default class Api { } } - async getItemInfoExt(productId, parseResponse = true) { + async getItemInfoExt(mediaKey, parseResponse = true) { // type assertion - if (productId) assertType(productId, 'string'); + if (mediaKey) assertType(mediaKey, 'string'); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'fDcn4b'; - const requestData = [productId, 1]; + const requestData = [mediaKey, 1]; try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); @@ -516,15 +522,15 @@ export default class Api { } } - async getBatchMediaInfo(productIdList, parseResponse = true) { + async getBatchMediaInfo(mediaKeyList, parseResponse = true) { // type assertion - if (productIdList) assertInstance(productIdList, Array); + if (mediaKeyList) assertInstance(mediaKeyList, Array); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'EWgK9e'; - productIdList = productIdList.map((id) => [id]); + mediaKeyList = mediaKeyList.map((id) => [id]); // prettier-ignore - const requestData = [[[productIdList], [[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, [], null, null, null, null, null, null, null, null, null, null, []]]]]; + const requestData = [[[mediaKeyList], [[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, [], null, null, null, null, null, null, null, null, null, null, []]]]]; try { let response = await this.makeApiRequest(rpcid, requestData); response = response[0][1]; diff --git a/src/api/parser.js b/src/api/parser.js index b63dd5f..0cafdae 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -5,11 +5,11 @@ export default function parser(data, rpcid) { function libraryItemParse(rawItemData) { return { - productId: rawItemData?.[0], + mediaKey: rawItemData?.[0], timestamp: rawItemData?.[2], - timestampTimezone: rawItemData?.[4], + timezoneOffset: rawItemData?.[4], creationTimestamp: rawItemData?.[5], - mediaId: rawItemData?.[3], + dedupKey: rawItemData?.[3], thumb: rawItemData?.[1]?.[0], resWidth: rawItemData?.[1]?.[1], resHeight: rawItemData?.[1]?.[2], @@ -44,10 +44,10 @@ export default function parser(data, rpcid) { function lockedFolderItemParse(rawItemData) { return { - productId: rawItemData?.[0], + mediaKey: rawItemData?.[0], timestamp: rawItemData?.[2], creationTimestamp: rawItemData?.[5], - mediaId: rawItemData?.[3], + dedupKey: rawItemData?.[3], duration: rawItemData?.at(-1)?.[76647426]?.[0], }; } @@ -61,7 +61,7 @@ export default function parser(data, rpcid) { function linkParse(rawLinkData) { return { - productId: rawLinkData?.[6], + mediaKey: rawLinkData?.[6], linkId: rawLinkData?.[17], itemCount: rawLinkData?.[3], }; @@ -76,9 +76,9 @@ export default function parser(data, rpcid) { function albumParse(rawAlbumData) { return { - productId: rawAlbumData?.[0], - albumId: rawAlbumData?.[6]?.[0], - name: rawAlbumData?.at(-1)?.[72930366]?.[1], + mediaKey: rawAlbumData?.[0], + viewerActorId: rawAlbumData?.[6]?.[0], + title: rawAlbumData?.at(-1)?.[72930366]?.[1], thumb: rawAlbumData?.[1]?.[0], itemCount: rawAlbumData?.at(-1)?.[72930366]?.[3], createdTimestamp: rawAlbumData?.at(-1)?.[72930366]?.[2]?.[4], @@ -97,14 +97,14 @@ export default function parser(data, rpcid) { function albumItemParse(rawItemData) { return { - albumProductId: rawItemData?.[0], + albumMediaKey: rawItemData?.[0], thumb: rawItemData?.[1]?.[0], resWidth: rawItemData[1]?.[1], resHeight: rawItemData[1]?.[2], timestamp: rawItemData?.[2], - timestampTimezone: rawItemData?.[4], + timezoneOffset: rawItemData?.[4], creationTimestamp: rawItemData?.[5], - mediaId: rawItemData?.[3], + dedupKey: rawItemData?.[3], isLivePhoto: rawItemData?.at(-1)?.[146008172] ? true : false, livePhotoDuration: rawItemData?.at(-1)?.[146008172]?.[1], duration: rawItemData?.at(-1)?.[76647426]?.[0], @@ -113,14 +113,14 @@ export default function parser(data, rpcid) { function trashItemParse(rawItemData) { return { - productId: rawItemData?.[0], + mediaKey: rawItemData?.[0], thumb: rawItemData?.[1]?.[0], resWidth: rawItemData?.[1]?.[1], resHeight: rawItemData?.[1]?.[2], timestamp: rawItemData?.[2], - timestampTimezone: rawItemData?.[4], + timezoneOffset: rawItemData?.[4], creationTimestamp: rawItemData?.[5], - mediaId: rawItemData?.[3], + dedupKey: rawItemData?.[3], duration: rawItemData?.at(-1)?.[76647426]?.[0], }; } @@ -141,11 +141,11 @@ export default function parser(data, rpcid) { function itemBulkMediaInfoParse(rawItemData) { return { - productId: rawItemData?.[0], + mediaKey: rawItemData?.[0], descriptionFull: rawItemData?.[1]?.[2], fileName: rawItemData?.[1]?.[3], timestamp: rawItemData?.[1]?.[6], - timestampTimezone: rawItemData?.[1]?.[7], + timezoneOffset: rawItemData?.[1]?.[7], creationTimestamp: rawItemData?.[1]?.[8], size: rawItemData?.[1]?.[9], takesUpSpace: rawItemData?.[1]?.at(-1)?.[0] === undefined ? null : rawItemData?.[1]?.at(-1)?.[0] === 1, @@ -177,12 +177,12 @@ export default function parser(data, rpcid) { source[1] = rawItemData[0]?.[27]?.[1]?.[2] ? sourceMapSecondary[rawItemData[0][27][1][2]] : null; return { - productId: rawItemData[0]?.[0], - mediaId: rawItemData[0]?.[11], + mediaKey: rawItemData[0]?.[0], + dedupKey: rawItemData[0]?.[11], descriptionFull: rawItemData[0]?.[1], fileName: rawItemData[0]?.[2], timestamp: rawItemData[0]?.[3], - timestampTimezone: rawItemData[0]?.[4], + timezoneOffset: rawItemData[0]?.[4], size: rawItemData[0]?.[5], resWidth: rawItemData[0]?.[6], resHeight: rawItemData[0]?.[7], @@ -205,12 +205,12 @@ export default function parser(data, rpcid) { function itemInfoParse(rawItemData) { return { - productId: rawItemData[0]?.[0], - mediaId: rawItemData[0]?.[3], + mediaKey: rawItemData[0]?.[0], + dedupKey: rawItemData[0]?.[3], resWidth: rawItemData[0]?.[1]?.[1], resHeight: rawItemData[0]?.[1]?.[2], timestamp: rawItemData[0]?.[2], - timestampTimezone: rawItemData[0]?.[4], + timezoneOffset: rawItemData[0]?.[4], creationTimestamp: rawItemData[0]?.[5], downloadUrl: rawItemData?.[1], downloadOriginalUrl: rawItemData?.[7], // url to download the original if item was modified after the upload @@ -224,7 +224,7 @@ export default function parser(data, rpcid) { isLivePhoto: rawItemData[0]?.[15]?.[146008172] ? true : false, livePhotoDuration: rawItemData[0]?.[15]?.[146008172]?.[1], livePhotoVideoDownloadUrl: rawItemData[0]?.[15]?.[146008172]?.[3], - movedToTrashTimestamp: rawItemData[0]?.[15]?.[225032867]?.[0], + trashTimestamp: rawItemData[0]?.[15]?.[225032867]?.[0], descriptionFull: rawItemData[10], thumb: rawItemData[12], }; diff --git a/src/gptk-core.js b/src/gptk-core.js index 78ac72e..07deda5 100644 --- a/src/gptk-core.js +++ b/src/gptk-core.js @@ -73,7 +73,7 @@ export default class Core { } log('Excluding album items'); mediaItems = mediaItems.filter((mediaItem) => { - return !itemsToExclude.some((excludeItem) => excludeItem.mediaId === mediaItem.mediaId); + return !itemsToExclude.some((excludeItem) => excludeItem.dedupKey === mediaItem.dedupKey); }); } if (mediaItems?.length && filter.excludeShared) { @@ -86,7 +86,7 @@ export default class Core { } log('Excluding shared items'); mediaItems = mediaItems.filter((mediaItem) => { - return !itemsToExclude.some((excludeItem) => excludeItem.mediaId === mediaItem.mediaId); + return !itemsToExclude.some((excludeItem) => excludeItem.dedupKey === mediaItem.dedupKey); }); } if (mediaItems?.length && filter.owned) mediaItems = this.filterOwned(mediaItems, filter); @@ -112,7 +112,7 @@ export default class Core { const mediaInfoData = await this.apiUtils.getBatchMediaInfoChunked(mediaItems); const extendedMediaItems = mediaItems.map((item) => { - const matchingInfoItem = mediaInfoData.find((infoItem) => infoItem.productId === item.productId); + const matchingInfoItem = mediaInfoData.find((infoItem) => infoItem.mediaKey === item.mediaKey); return { ...item, ...matchingInfoItem }; }); return extendedMediaItems; diff --git a/src/ui/logic/action-bar.js b/src/ui/logic/action-bar.js index 92e7470..61f395b 100644 --- a/src/ui/logic/action-bar.js +++ b/src/ui/logic/action-bar.js @@ -49,8 +49,8 @@ async function runAction(actionId) { let targetAlbum = null; let newTargetAlbumName = null; if (actionId === 'toExistingAlbum') { - const albumProductId = document.getElementById(action?.targetId)?.value; - targetAlbum = getFromStorage('albums').find((album) => album.productId === albumProductId); + const albumMediaKey = document.getElementById(action?.targetId)?.value; + targetAlbum = getFromStorage('albums').find((album) => album.mediaKey === albumMediaKey); } else { newTargetAlbumName = document.getElementById(action?.targetId)?.value; } diff --git a/src/ui/logic/album-selects-update.js b/src/ui/logic/album-selects-update.js index a0cb371..545f50c 100644 --- a/src/ui/logic/album-selects-update.js +++ b/src/ui/logic/album-selects-update.js @@ -12,7 +12,7 @@ export function addAlbums(albums) { for (const album of albums) { if (parseInt(album.itemCount) === 0 && !addEmpty) continue; const option = document.createElement('option'); - option.value = album.productId; + option.value = album.mediaKey; option.title = `Name: ${album.name}\nItems: ${album.itemCount}`; option.textContent = album.name; if (album.isShared) option.classList.add('shared'); From 9f8487bfec42a19bbb0bc96b2a1b2e122464a2b3 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 04:01:15 +0300 Subject: [PATCH 04/21] new parsed album properties --- src/api/parser.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/api/parser.js b/src/api/parser.js index 0cafdae..672ad7d 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -125,10 +125,31 @@ export default function parser(data, rpcid) { }; } + function memberParse(rawMemberData) { + return { + actorId: rawMemberData?.[0], + gaiaId: rawMemberData?.[1], + name: rawMemberData?.[11][0], + gender: rawMemberData?.[11][2], + profiePhotoUrl: rawMemberData?.[12][0], + }; + } + function albumItemsPage(data) { return { items: data?.[1]?.map((rawItemData) => albumItemParse(rawItemData)), nextPageId: data?.[2], + mediaKey: data?.[3][0], + title: data?.[3][1], + startTimestamp:data?.[3][2][5], + endTimestamp:data?.[3][2][6], + lastActivityTimestamp:data?.[3][2][7], + createdTimestamp:data?.[3][2][8], + newestOperationTimestamp:data?.[3][2][9], + totalItemCount: data?.[3][2][21], + authKey:data?.[3][19], + owner: memberParse(data?.[3][5]), + members: data?.[3][9]?.map((memberData) => memberParse(memberData)), }; } From 6eeabc1d9940ba474057b436cf4cbb346da36da2 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 04:30:02 +0300 Subject: [PATCH 05/21] minor refactor of removeItemsFromSharedAlbum, setItemGeoData, deleteItemGeoData --- src/api/api.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/api/api.js b/src/api/api.js index 404d315..1491148 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -396,15 +396,15 @@ export default class Api { } } - async removeItemsFromSharedAlbum(albumMediaKey, itemProductIdList) { + async removeItemsFromSharedAlbum(albumMediaKey, mediaKeyArray) { // type assertion if (albumMediaKey) assertType(albumMediaKey, 'string'); - if (itemProductIdList) assertInstance(itemProductIdList, Array); + if (mediaKeyArray) assertInstance(mediaKeyArray, Array); const rpcid = 'LjmOue'; const requestData = [ [albumMediaKey], - [itemProductIdList], + [mediaKeyArray], [[null, null, null, [null, [], []], null, null, null, null, null, null, null, null, null, []]], ]; try { @@ -416,9 +416,9 @@ export default class Api { } } - async setItemGeoData(dedupKey, center, visible1, visible2, scale, gMapsPlaceId) { + async setItemGeoData(dedupKeyArray, center, visible1, visible2, scale, gMapsPlaceId) { // type assertion - if (dedupKey) assertType(dedupKey, 'string'); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); if (center) assertInstance(center, Array); if (visible1) assertInstance(visible1, Array); if (visible2) assertInstance(visible2, Array); @@ -428,7 +428,8 @@ export default class Api { // every point is an array of coordinates, every coordinate is 9 digit-long int // coordinates and scale can be extracted from mapThumb, but gMapsPlaceId is not exposed in GP const rpcid = 'EtUHOe'; - const requestData = [[[null, dedupKey]], [2, center, [visible1, visible2], [null, null, scale], gMapsPlaceId]]; + dedupKeyArray = dedupKeyArray.map((dedupKey) => [null, dedupKey]); + const requestData = [dedupKeyArray, [2, center, [visible1, visible2], [null, null, scale], gMapsPlaceId]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -438,12 +439,13 @@ export default class Api { } } - async deleteItemGeoData(dedupKey) { + async deleteItemGeoData(dedupKeyArray) { // type assertion - if (dedupKey) assertType(dedupKey, 'string'); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); const rpcid = 'EtUHOe'; - const requestData = [[[null, dedupKey]], [1]]; + dedupKeyArray = dedupKeyArray.map((dedupKey) => [null, dedupKey]); + const requestData = [dedupKeyArray, [1]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; From 83e0568cbecf1a80d7ac9f09f207a38464f010e0 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 04:38:00 +0300 Subject: [PATCH 06/21] replace list -> array --- src/api/api-utils.js | 40 ++++++++++++------------- src/api/api.js | 70 ++++++++++++++++++++++---------------------- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/api/api-utils.js b/src/api/api-utils.js index cd5a919..5342623 100644 --- a/src/api/api-utils.js +++ b/src/api/api-utils.js @@ -104,53 +104,53 @@ export default class ApiUtils { async moveToLockedFolder(mediaItems) { log(`Moving ${mediaItems.length} items to locked folder`); const isSuccess = (result) => Array.isArray(result); - const dedupKeyList = mediaItems.map((item) => item.dedupKey); - await this.executeWithConcurrency(this.api.moveToLockedFolder, isSuccess, this.lockedFolderOpSize, dedupKeyList); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.moveToLockedFolder, isSuccess, this.lockedFolderOpSize, dedupKeyArray); } async removeFromLockedFolder(mediaItems) { log(`Moving ${mediaItems.length} items out of locked folder`); const isSuccess = (result) => Array.isArray(result); - const dedupKeyList = mediaItems.map((item) => item.dedupKey); - await this.executeWithConcurrency(this.api.removeFromLockedFolder, isSuccess, this.lockedFolderOpSize, dedupKeyList); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.removeFromLockedFolder, isSuccess, this.lockedFolderOpSize, dedupKeyArray); } async moveToTrash(mediaItems) { log(`Moving ${mediaItems.length} items to trash`); const isSuccess = (result) => Array.isArray(result); - const dedupKeyList = mediaItems.map((item) => item.dedupKey); - await this.executeWithConcurrency(this.api.moveItemsToTrash, isSuccess, this.operationSize, dedupKeyList); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.moveItemsToTrash, isSuccess, this.operationSize, dedupKeyArray); } async restoreFromTrash(trashItems) { log(`Restoring ${trashItems.length} items from trash`); const isSuccess = (result) => Array.isArray(result); - const dedupKeyList = trashItems.map((item) => item.dedupKey); - await this.executeWithConcurrency(this.api.restoreFromTrash, isSuccess, this.operationSize, dedupKeyList); + const dedupKeyArray = trashItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.restoreFromTrash, isSuccess, this.operationSize, dedupKeyArray); } async sendToArchive(mediaItems) { log(`Sending ${mediaItems.length} items to archive`); const isSuccess = (result) => Array.isArray(result); mediaItems = mediaItems.filter((item) => item?.isArchived !== true); - const dedupKeyList = mediaItems.map((item) => item.dedupKey); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); if (!mediaItems) { log('All target items are already archived!'); return; } - await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, dedupKeyList, true); + await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, dedupKeyArray, true); } async unArchive(mediaItems) { log(`Removing ${mediaItems.length} items from archive`); const isSuccess = (result) => Array.isArray(result); mediaItems = mediaItems.filter((item) => item?.isArchived !== false); - const dedupKeyList = mediaItems.map((item) => item.dedupKey); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); if (!mediaItems) { log('All target items are not archived!'); return; } - await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, dedupKeyList, false); + await this.executeWithConcurrency(this.api.setArchive, isSuccess, this.operationSize, dedupKeyArray, false); } async setAsFavorite(mediaItems) { @@ -161,8 +161,8 @@ export default class ApiUtils { log('All target items are already favorite!'); return; } - const dedupKeyList = mediaItems.map((item) => item.dedupKey); - await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, dedupKeyList, true); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, dedupKeyArray, true); } async unFavorite(mediaItems) { @@ -173,15 +173,15 @@ export default class ApiUtils { log('All target items are not favorite!'); return; } - const dedupKeyList = mediaItems.map((item) => item.dedupKey); - await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, dedupKeyList, false); + const dedupKeyArray = mediaItems.map((item) => item.dedupKey); + await this.executeWithConcurrency(this.api.setFavorite, isSuccess, this.operationSize, dedupKeyArray, false); } async addToExistingAlbum(mediaItems, targetAlbum) { log(`Adding ${mediaItems.length} items to album "${targetAlbum.name}"`); const isSuccess = (result) => Array.isArray(result); - const mediaKeyList = mediaItems.map((item) => item.mediaKey); + const mediaKeyArray = mediaItems.map((item) => item.mediaKey); const addItemFunction = targetAlbum.isShared ? this.api.addItemsToSharedAlbum : this.api.addItemsToAlbum; @@ -189,7 +189,7 @@ export default class ApiUtils { addItemFunction, isSuccess, this.operationSize, - mediaKeyList, + mediaKeyArray, targetAlbum.mediaKey ); } @@ -205,12 +205,12 @@ export default class ApiUtils { async getBatchMediaInfoChunked(mediaItems) { log('Getting items\' media info'); - const mediaKeyList = mediaItems.map((item) => item.mediaKey); + const mediaKeyArray = mediaItems.map((item) => item.mediaKey); const mediaInfoData = await this.executeWithConcurrency( this.api.getBatchMediaInfo, null, this.infoSize, - mediaKeyList + mediaKeyArray ); return mediaInfoData; } diff --git a/src/api/api.js b/src/api/api.js index 1491148..309b045 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -156,12 +156,12 @@ export default class Api { } } - async moveItemsToTrash(dedupKeyList) { + async moveItemsToTrash(dedupKeyArray) { // type assertion - if (dedupKeyList) assertInstance(dedupKeyList, Array); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); const rpcid = 'XwAOJf'; - const requestData = [null, 1, dedupKeyList, 3]; + const requestData = [null, 1, dedupKeyArray, 3]; // note: It seems that '3' here corresponds to items' location try { const response = await this.makeApiRequest(rpcid, requestData); @@ -172,12 +172,12 @@ export default class Api { } } - async restoreFromTrash(dedupKeyList) { + async restoreFromTrash(dedupKeyArray) { // type assertion - if (dedupKeyList) assertInstance(dedupKeyList, Array); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); const rpcid = 'XwAOJf'; - const requestData = [null, 3, dedupKeyList, 2]; + const requestData = [null, 3, dedupKeyArray, 2]; try { const response = await this.makeApiRequest(rpcid, requestData); return response[0]; @@ -247,14 +247,14 @@ export default class Api { } } - async removeItemsFromAlbum(itemalbumMediaKeyList) { + async removeItemsFromAlbum(itemalbumMediaKeyArray) { // regular mediaKey's won't cut it, you need to get them from an album // type assertion - if (itemalbumMediaKeyList) assertInstance(itemalbumMediaKeyList, Array); + if (itemalbumMediaKeyArray) assertInstance(itemalbumMediaKeyArray, Array); const rpcid = 'ycV3Nd'; - const requestData = [itemalbumMediaKeyList]; + const requestData = [itemalbumMediaKeyArray]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -281,19 +281,19 @@ export default class Api { } } - async addItemsToAlbum(mediaKeyList, albumId = null, albumName = null) { + async addItemsToAlbum(mediaKeyArray, albumId = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one // type assertion - if (mediaKeyList) assertInstance(mediaKeyList, Array); + if (mediaKeyArray) assertInstance(mediaKeyArray, Array); if (albumId) assertType(albumId, 'string'); if (albumName) assertType(albumName, 'string'); const rpcid = 'E1Cajb'; let requestData = null; - if (albumName) requestData = [mediaKeyList, null, albumName]; - else if (albumId) requestData = [mediaKeyList, albumId]; + if (albumName) requestData = [mediaKeyArray, null, albumName]; + else if (albumId) requestData = [mediaKeyArray, albumId]; try { const response = await this.makeApiRequest(rpcid, requestData); @@ -304,19 +304,19 @@ export default class Api { } } - async addItemsToSharedAlbum(mediaKeyList, albumId = null, albumName = null) { + async addItemsToSharedAlbum(mediaKeyArray, albumId = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one // type assertion - if (mediaKeyList) assertInstance(mediaKeyList, Array); + if (mediaKeyArray) assertInstance(mediaKeyArray, Array); if (albumId) assertType(albumId, 'string'); if (albumName) assertType(albumName, 'string'); const rpcid = 'laUYf'; let requestData = null; - if (albumName) requestData = [mediaKeyList, null, albumName]; - else if (albumId) requestData = [albumId, [2, null, mediaKeyList.map((id) => [[id]]), null, null, null, [1]]]; + if (albumName) requestData = [mediaKeyArray, null, albumName]; + else if (albumId) requestData = [albumId, [2, null, mediaKeyArray.map((id) => [[id]]), null, null, null, [1]]]; try { const response = await this.makeApiRequest(rpcid, requestData); @@ -327,16 +327,16 @@ export default class Api { } } - async setFavorite(dedupKeyList, action = true) { + async setFavorite(dedupKeyArray, action = true) { // type assertion - if (dedupKeyList) assertInstance(dedupKeyList, Array); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); if (action) assertType(action, 'boolean'); if (action === true) action = 1; //set favorite else if (action === false) action = 2; //un favorite - dedupKeyList = dedupKeyList.map((item) => [null, item]); + dedupKeyArray = dedupKeyArray.map((item) => [null, item]); const rpcid = 'Ftfh0'; - const requestData = [dedupKeyList, [action]]; + const requestData = [dedupKeyArray, [action]]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -346,17 +346,17 @@ export default class Api { } } - async setArchive(dedupKeyList, action = true) { + async setArchive(dedupKeyArray, action = true) { // type assertion - if (dedupKeyList) assertInstance(dedupKeyList, Array); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); if (action) assertType(action, 'boolean'); if (action === true) action = 1; // send to archive else if (action === false) action = 2; // un archive - dedupKeyList = dedupKeyList.map((item) => [null, [action], [null, item]]); + dedupKeyArray = dedupKeyArray.map((item) => [null, [action], [null, item]]); const rpcid = 'w7TP3c'; - const requestData = [dedupKeyList, null, 1]; + const requestData = [dedupKeyArray, null, 1]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -366,12 +366,12 @@ export default class Api { } } - async moveToLockedFolder(dedupKeyList) { + async moveToLockedFolder(dedupKeyArray) { // type assertion - if (dedupKeyList) assertInstance(dedupKeyList, Array); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); const rpcid = 'StLnCe'; - const requestData = [dedupKeyList, []]; + const requestData = [dedupKeyArray, []]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -381,12 +381,12 @@ export default class Api { } } - async removeFromLockedFolder(dedupKeyList) { + async removeFromLockedFolder(dedupKeyArray) { // type assertion - if (dedupKeyList) assertInstance(dedupKeyList, Array); + if (dedupKeyArray) assertInstance(dedupKeyArray, Array); const rpcid = 'Pp2Xxe'; - const requestData = [dedupKeyList]; + const requestData = [dedupKeyArray]; try { const response = await this.makeApiRequest(rpcid, requestData); return response; @@ -524,15 +524,15 @@ export default class Api { } } - async getBatchMediaInfo(mediaKeyList, parseResponse = true) { + async getBatchMediaInfo(mediaKeyArray, parseResponse = true) { // type assertion - if (mediaKeyList) assertInstance(mediaKeyList, Array); + if (mediaKeyArray) assertInstance(mediaKeyArray, Array); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'EWgK9e'; - mediaKeyList = mediaKeyList.map((id) => [id]); + mediaKeyArray = mediaKeyArray.map((id) => [id]); // prettier-ignore - const requestData = [[[mediaKeyList], [[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, [], null, null, null, null, null, null, null, null, null, null, []]]]]; + const requestData = [[[mediaKeyArray], [[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, [], null, null, null, null, null, null, null, null, null, null, []]]]]; try { let response = await this.makeApiRequest(rpcid, requestData); response = response[0][1]; From 593a08108842f5b39032e5b2440aefd13558169a Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 05:16:56 +0300 Subject: [PATCH 07/21] disambiguate albumId as albumMediaKey --- src/api/api-utils.js | 8 ++++---- src/api/api.js | 12 ++++++------ src/gptk-core.js | 8 ++++---- src/ui/markup/gptk-main-template.html | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/api/api-utils.js b/src/api/api-utils.js index 5342623..cebe8c3 100644 --- a/src/api/api-utils.js +++ b/src/api/api-utils.js @@ -81,8 +81,8 @@ export default class ApiUtils { return await this.getAllItems(this.api.getAlbumPage, sharedLinkId); } - async getAllMediaInAlbum(albumId) { - return await this.getAllItems(this.api.getAlbumPage, albumId); + async getAllMediaInAlbum(albumMediaKey) { + return await this.getAllItems(this.api.getAlbumPage, albumMediaKey); } async getAllTrashItems() { @@ -178,7 +178,7 @@ export default class ApiUtils { } async addToExistingAlbum(mediaItems, targetAlbum) { - log(`Adding ${mediaItems.length} items to album "${targetAlbum.name}"`); + log(`Adding ${mediaItems.length} items to album "${targetalbum.title}"`); const isSuccess = (result) => Array.isArray(result); const mediaKeyArray = mediaItems.map((item) => item.mediaKey); @@ -197,7 +197,7 @@ export default class ApiUtils { async addToNewAlbum(mediaItems, targetAlbumName) { log(`Creating new album "${targetAlbumName}"`); const album = {}; - album.name = targetAlbumName; + album.title = targetAlbumName; album.shared = false; album.mediaKey = await this.api.createAlbum(targetAlbumName); await this.addToExistingAlbum(mediaItems, album); diff --git a/src/api/api.js b/src/api/api.js index 309b045..9d65dda 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -281,19 +281,19 @@ export default class Api { } } - async addItemsToAlbum(mediaKeyArray, albumId = null, albumName = null) { + async addItemsToAlbum(mediaKeyArray, albumMediaKey = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one // type assertion if (mediaKeyArray) assertInstance(mediaKeyArray, Array); - if (albumId) assertType(albumId, 'string'); + if (albumMediaKey) assertType(albumMediaKey, 'string'); if (albumName) assertType(albumName, 'string'); const rpcid = 'E1Cajb'; let requestData = null; if (albumName) requestData = [mediaKeyArray, null, albumName]; - else if (albumId) requestData = [mediaKeyArray, albumId]; + else if (albumMediaKey) requestData = [mediaKeyArray, albumMediaKey]; try { const response = await this.makeApiRequest(rpcid, requestData); @@ -304,19 +304,19 @@ export default class Api { } } - async addItemsToSharedAlbum(mediaKeyArray, albumId = null, albumName = null) { + async addItemsToSharedAlbum(mediaKeyArray, albumMediaKey = null, albumName = null) { // supply album ID for adding to an existing album, or a name for a new one // type assertion if (mediaKeyArray) assertInstance(mediaKeyArray, Array); - if (albumId) assertType(albumId, 'string'); + if (albumMediaKey) assertType(albumMediaKey, 'string'); if (albumName) assertType(albumName, 'string'); const rpcid = 'laUYf'; let requestData = null; if (albumName) requestData = [mediaKeyArray, null, albumName]; - else if (albumId) requestData = [albumId, [2, null, mediaKeyArray.map((id) => [[id]]), null, null, null, [1]]]; + else if (albumMediaKey) requestData = [albumMediaKey, [2, null, mediaKeyArray.map((id) => [[id]]), null, null, null, [1]]]; try { const response = await this.makeApiRequest(rpcid, requestData); diff --git a/src/gptk-core.js b/src/gptk-core.js index 07deda5..2dd2e25 100644 --- a/src/gptk-core.js +++ b/src/gptk-core.js @@ -47,9 +47,9 @@ export default class Core { throw new Error('no target album!'); } filter.albumsInclude = Array.isArray(filter.albumsInclude) ? filter.albumsInclude : [filter.albumsInclude]; - for (const albumId of filter.albumsInclude) { + for (const albumMediaKey of filter.albumsInclude) { log('Getting album items'); - mediaItems.push(...(await this.apiUtils.getAllMediaInAlbum(albumId))); + mediaItems.push(...(await this.apiUtils.getAllMediaInAlbum(albumMediaKey))); } } @@ -67,9 +67,9 @@ export default class Core { const itemsToExclude = []; filter.albumsExclude = Array.isArray(filter.albumsExclude) ? filter.albumsExclude : [filter.albumsExclude]; - for (const albumId of filter.albumsExclude) { + for (const albumMediaKey of filter.albumsExclude) { log('Getting album items to exclude'); - itemsToExclude.push(...(await this.apiUtils.getAllMediaInAlbum(albumId))); + itemsToExclude.push(...(await this.apiUtils.getAllMediaInAlbum(albumMediaKey))); } log('Excluding album items'); mediaItems = mediaItems.filter((mediaItem) => { diff --git a/src/ui/markup/gptk-main-template.html b/src/ui/markup/gptk-main-template.html index 3aea6dd..ee4b83c 100644 --- a/src/ui/markup/gptk-main-template.html +++ b/src/ui/markup/gptk-main-template.html @@ -101,7 +101,7 @@ d="M482-160q-134 0-228-93t-94-227v-7l-64 64-56-56 160-160 160 160-56 56-64-64v7q0 100 70.5 170T482-240q26 0 51-6t49-18l60 60q-38 22-78 33t-82 11Zm278-161L600-481l56-56 64 64v-7q0-100-70.5-170T478-720q-26 0-51 6t-49 18l-60-60q38-22 78-33t82-11q134 0 228 93t94 227v7l64-64 56 56-160 160Z" /> - From d9dc80b75e633d4eaa8ed968bcf5bfa8a9c13a79 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 05:17:29 +0300 Subject: [PATCH 08/21] chagned missed album.name to album.title --- src/ui/logic/album-selects-update.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/logic/album-selects-update.js b/src/ui/logic/album-selects-update.js index 545f50c..a511572 100644 --- a/src/ui/logic/album-selects-update.js +++ b/src/ui/logic/album-selects-update.js @@ -13,8 +13,8 @@ export function addAlbums(albums) { if (parseInt(album.itemCount) === 0 && !addEmpty) continue; const option = document.createElement('option'); option.value = album.mediaKey; - option.title = `Name: ${album.name}\nItems: ${album.itemCount}`; - option.textContent = album.name; + option.title = `Name: ${album.title}\nItems: ${album.itemCount}`; + option.textContent = album.title; if (album.isShared) option.classList.add('shared'); albumSelect.appendChild(option); } From 76dee44b6259591127c43d9662d8eff1605f6a2a Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 05:18:56 +0300 Subject: [PATCH 09/21] make itemInfo api methods work with shared album items --- src/api/api.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/api.js b/src/api/api.js index 9d65dda..fad54ce 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -490,13 +490,14 @@ export default class Api { } } - async getItemInfo(mediaKey, parseResponse = true) { + async getItemInfo(mediaKey, parseResponse = true, albumMediaKey = null, authKey = null) { // type assertion if (mediaKey) assertType(mediaKey, 'string'); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'VrseUb'; - const requestData = [mediaKey, null, null, 1]; + + const requestData = [mediaKey, null, authKey, null, albumMediaKey]; try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); @@ -507,13 +508,13 @@ export default class Api { } } - async getItemInfoExt(mediaKey, parseResponse = true) { + async getItemInfoExt(mediaKey, parseResponse = true, authKey = null) { // type assertion if (mediaKey) assertType(mediaKey, 'string'); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'fDcn4b'; - const requestData = [mediaKey, 1]; + const requestData = [mediaKey, 1, authKey]; try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); From 006ab1670d0b602b4457421f622cfda1e4926848 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 05:19:37 +0300 Subject: [PATCH 10/21] itemInfo owner parse --- src/api/parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/parser.js b/src/api/parser.js index 672ad7d..716d35d 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -231,6 +231,7 @@ export default function parser(data, rpcid) { resWidth: rawItemData[0]?.[1]?.[1], resHeight: rawItemData[0]?.[1]?.[2], timestamp: rawItemData[0]?.[2], + owner: memberParse(rawItemData[3]), timezoneOffset: rawItemData[0]?.[4], creationTimestamp: rawItemData[0]?.[5], downloadUrl: rawItemData?.[1], From 9daa8e3d4f14740a5f670838cb533f964e1ca80f Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:31:57 +0300 Subject: [PATCH 11/21] simplified getAlbumPage --- src/api/api.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/api/api.js b/src/api/api.js index fad54ce..9bb72f8 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -230,13 +230,7 @@ export default class Api { if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'snAcKc'; - let requestData = null; - if (authKey){ - requestData = [albumMediaKey, pageId, null, null, 1]; - }else{ - requestData = [albumMediaKey,pageId,null,authKey]; - } - + const requestData = [albumMediaKey, pageId, null, authKey]; try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); From 94347088f46d3526c366cd9ca3dbc04c0f019745 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:32:24 +0300 Subject: [PATCH 12/21] formatting --- src/api/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/api.js b/src/api/api.js index 9bb72f8..e7740dd 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -221,7 +221,7 @@ export default class Api { } } - async getAlbumPage(albumMediaKey, pageId = null, authKey=null, parseResponse = true) { + async getAlbumPage(albumMediaKey, pageId = null, authKey = null, parseResponse = true) { // get items of an album or a shared link with the given id // type assertion From e8afd63c92aff2c128060851066c283b44799e2e Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:54:06 +0300 Subject: [PATCH 13/21] member -> actor --- src/api/parser.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/api/parser.js b/src/api/parser.js index 716d35d..ee1f274 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -125,13 +125,13 @@ export default function parser(data, rpcid) { }; } - function memberParse(rawMemberData) { + function actorParse(rawActorData) { return { - actorId: rawMemberData?.[0], - gaiaId: rawMemberData?.[1], - name: rawMemberData?.[11][0], - gender: rawMemberData?.[11][2], - profiePhotoUrl: rawMemberData?.[12][0], + actorId: rawActorData?.[0], + gaiaId: rawActorData?.[1], + name: rawActorData?.[11]?.[0], + gender: rawActorData?.[11]?.[2], + profiePhotoUrl: rawActorData?.[12]?.[0], }; } @@ -147,9 +147,9 @@ export default function parser(data, rpcid) { createdTimestamp:data?.[3][2][8], newestOperationTimestamp:data?.[3][2][9], totalItemCount: data?.[3][2][21], - authKey:data?.[3][19], - owner: memberParse(data?.[3][5]), - members: data?.[3][9]?.map((memberData) => memberParse(memberData)), + authKey: data?.[3][19], + owner: actorParse(data?.[3][5]), + members: data?.[3][9]?.map((memberData) => actorParse(memberData)), }; } @@ -231,7 +231,7 @@ export default function parser(data, rpcid) { resWidth: rawItemData[0]?.[1]?.[1], resHeight: rawItemData[0]?.[1]?.[2], timestamp: rawItemData[0]?.[2], - owner: memberParse(rawItemData[3]), + owner: actorParse(rawItemData[3]), timezoneOffset: rawItemData[0]?.[4], creationTimestamp: rawItemData[0]?.[5], downloadUrl: rawItemData?.[1], From 4b7a1ddf75ddc7098f7c2793ee04853a7cc30182 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:54:58 +0300 Subject: [PATCH 14/21] remove owner info from extended info --- src/api/parser.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/api/parser.js b/src/api/parser.js index ee1f274..250c12d 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -197,6 +197,16 @@ export default function parser(data, rpcid) { source[1] = rawItemData[0]?.[27]?.[1]?.[2] ? sourceMapSecondary[rawItemData[0][27][1][2]] : null; + // this is a mess, for some items it is in 27, for some in 28 + // for now it is better to just ignore it and use info from itemInfo, it is much more reliable + // let owner = null; + // if (rawItemData[0]?.[27]?.length > 0){ + // owner = actorParse(rawItemData[0]?.[27]?.[3]?.[0]?.[11]?.[0] || rawItemData[0]?.[27]?.[4]?.[0]?.[11]?.[0]); + // } + // if (!owner){ + // owner = actorParse(rawItemData[0]?.[28]); + // } + return { mediaKey: rawItemData[0]?.[0], dedupKey: rawItemData[0]?.[11], @@ -214,8 +224,7 @@ export default function parser(data, rpcid) { spaceTaken: rawItemData[0]?.[30]?.[1], isOriginalQuality: rawItemData[0]?.[30]?.[2] === undefined ? null : rawItemData[0][30][2] === 2, savedToYourPhotos: rawItemData[0]?.[12].filter((subArray) => subArray.includes(20)).length === 0, - sharedByUserName: rawItemData[0]?.[27]?.[3]?.[0]?.[11]?.[0] || rawItemData[0]?.[27]?.[4]?.[0]?.[11]?.[0], - sharedByUserId: rawItemData[0]?.[27]?.[3]?.[0]?.[1] || rawItemData[0]?.[27]?.[4]?.[0]?.[1], + // owner: owner, geoLocation: { coordinates: rawItemData[0]?.[13]?.[0], name: rawItemData[0]?.[13]?.[2]?.[0]?.[1]?.[0]?.[0], From 8615b00b655349ea3ed99e1cd813765e5ae80ccd Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:55:08 +0300 Subject: [PATCH 15/21] formatting --- src/api/parser.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/api/parser.js b/src/api/parser.js index 250c12d..5f4b384 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -141,11 +141,11 @@ export default function parser(data, rpcid) { nextPageId: data?.[2], mediaKey: data?.[3][0], title: data?.[3][1], - startTimestamp:data?.[3][2][5], - endTimestamp:data?.[3][2][6], - lastActivityTimestamp:data?.[3][2][7], - createdTimestamp:data?.[3][2][8], - newestOperationTimestamp:data?.[3][2][9], + startTimestamp: data?.[3][2][5], + endTimestamp: data?.[3][2][6], + lastActivityTimestamp: data?.[3][2][7], + createdTimestamp: data?.[3][2][8], + newestOperationTimestamp: data?.[3][2][9], totalItemCount: data?.[3][2][21], authKey: data?.[3][19], owner: actorParse(data?.[3][5]), From a24002aee6f5e7317ce4b7e621b7730ff88babed Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:55:54 +0300 Subject: [PATCH 16/21] feat: methods to get download urls --- src/api/api.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/api/parser.js | 10 +++++++++ 2 files changed, 67 insertions(+) diff --git a/src/api/api.js b/src/api/api.js index e7740dd..eba6141 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -390,6 +390,63 @@ export default class Api { } } + // there are at least two rpcid's that are used for file downloading + // getDownloadUrl uses `pLFTfd` + // getDownloadToken uses `yCLA7` + // `pLFTfd` is simple, send array of keys, get a dl url, can use authKey to download shared media + // getDownloadToken recives a token, which is then used to check if the dl url is ready with checkDownloadToken + // using `pLFTfd` seems like a no-brainer, but `yCLA7` is also implemeted, just in case + + async getDownloadUrl(mediaKeyArray, authKey = null) { + // type assertion + if (mediaKeyArray) assertInstance(mediaKeyArray, Array); + + const rpcid = 'pLFTfd'; + const requestData = [mediaKeyArray, null, authKey]; + try { + const response = await this.makeApiRequest(rpcid, requestData); + return response[0]; + } catch (error) { + console.error('Error in getDownloadUrl:', error); + throw error; + } + } + + async getDownloadToken(mediaKeyArray) { + // use the token with checkDownloadToken to check if DL ulr is ready + + // type assertion + if (mediaKeyArray) assertInstance(mediaKeyArray, Array); + + const rpcid = 'yCLA7'; + const requestData = [[mediaKeyArray]]; + try { + const response = await this.makeApiRequest(rpcid, requestData); + return response[0]; + } catch (error) { + console.error('Error in getDownloadToken:', error); + throw error; + } + } + + async checkDownloadToken(dlToken, parseResponse = true) { + // returns dl url if one found + + // type assertion + if (dlToken) assertType(dlToken, 'string'); + + const rpcid = 'dnv2s'; + const requestData = [[dlToken]]; + try { + const response = await this.makeApiRequest(rpcid, requestData); + if (parseResponse) return parser(response, rpcid); + return response; + } catch (error) { + console.error('Error in checkDownloadToken:', error); + throw error; + } + } + async removeItemsFromSharedAlbum(albumMediaKey, mediaKeyArray) { // type assertion if (albumMediaKey) assertType(albumMediaKey, 'string'); diff --git a/src/api/parser.js b/src/api/parser.js index 5f4b384..db553c7 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -265,6 +265,15 @@ export default function parser(data, rpcid) { return data.map((rawItemData) => itemBulkMediaInfoParse(rawItemData)); } + function downloadTokenCheckParse(data) { + return { + fileName: data?.[0]?.[0]?.[0]?.[2]?.[0]?.[0], + downloadUrl: data?.[0]?.[0]?.[0]?.[2]?.[0]?.[1], + downloadSize: data?.[0]?.[0]?.[0]?.[2]?.[0]?.[2], + unzippedSize: data?.[0]?.[0]?.[0]?.[2]?.[0]?.[3], + }; + } + if (!data?.length) return null; if (rpcid === 'lcxiM') return libraryTimelinePage(data); if (rpcid === 'nMFwOc') return lockedFolderPage(data); @@ -276,4 +285,5 @@ export default function parser(data, rpcid) { if (rpcid === 'VrseUb') return itemInfoParse(data); if (rpcid === 'fDcn4b') return itemInfoExtParse(data); if (rpcid === 'EWgK9e') return bulkMediaInfo(data); + if (rpcid === 'dnv2s') return downloadTokenCheckParse(data); } From 31343bab5d84e4cabdc58090535b6f37d802d842 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Sat, 29 Jun 2024 07:28:59 +0300 Subject: [PATCH 17/21] albums page size as arg --- src/api/api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/api.js b/src/api/api.js index eba6141..42ddb11 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -204,13 +204,13 @@ export default class Api { } } - async getAlbums(pageId = null, parseResponse = true) { + async getAlbums(pageId = null, parseResponse = true, pageSize = 100) { // type assertion if (pageId) assertType(pageId, 'string'); if (parseResponse) assertType(parseResponse, 'boolean'); const rpcid = 'Z5xsfc'; - const requestData = [pageId, null, null, null, 1, null, null, 100]; + const requestData = [pageId, null, null, null, 1, null, null, pageSize]; try { const response = await this.makeApiRequest(rpcid, requestData); if (parseResponse) return parser(response, rpcid); From bddba90ba8bdcc39c19cb80ffda1a8120e1c7d73 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Sat, 29 Jun 2024 07:46:27 +0300 Subject: [PATCH 18/21] fix: #5 --- src/api/api-utils.js | 12 +++++------- src/ui/logic/album-selects-controls.js | 4 ++-- src/ui/markup/gptk-main-template.html | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/api/api-utils.js b/src/api/api-utils.js index cebe8c3..fd75749 100644 --- a/src/api/api-utils.js +++ b/src/api/api-utils.js @@ -58,13 +58,11 @@ export default class ApiUtils { do { if (!this.core.isProcessRunning) return; const page = await apiMethod.call(this.api, ...args, nextPageId); - if (!page?.items) { - log('No items found!', 'error'); - return []; + if (page?.items?.length > 0) { + log(`Found ${page.items.length} items`); + items.push(...page.items); } - items.push(...page.items); - log(`Found ${page.items.length} items`); - nextPageId = page.nextPageId; + nextPageId = page?.nextPageId; } while (nextPageId); return items; } @@ -178,7 +176,7 @@ export default class ApiUtils { } async addToExistingAlbum(mediaItems, targetAlbum) { - log(`Adding ${mediaItems.length} items to album "${targetalbum.title}"`); + log(`Adding ${mediaItems.length} items to album "${targetAlbum.title}"`); const isSuccess = (result) => Array.isArray(result); const mediaKeyArray = mediaItems.map((item) => item.mediaKey); diff --git a/src/ui/logic/album-selects-controls.js b/src/ui/logic/album-selects-controls.js index ac63875..b20e9e0 100644 --- a/src/ui/logic/album-selects-controls.js +++ b/src/ui/logic/album-selects-controls.js @@ -76,8 +76,8 @@ async function refreshAlbums() { addAlbums(albums); saveToStorage('albums', albums); log('Albums Refreshed'); - } catch { - log('Error refreshing albums', 'error'); + } catch (e){ + log(`Error refreshing albums ${e}`, 'error'); } core.isProcessRunning = false; updateUI(); diff --git a/src/ui/markup/gptk-main-template.html b/src/ui/markup/gptk-main-template.html index ee4b83c..ed54735 100644 --- a/src/ui/markup/gptk-main-template.html +++ b/src/ui/markup/gptk-main-template.html @@ -101,7 +101,7 @@ d="M482-160q-134 0-228-93t-94-227v-7l-64 64-56-56 160-160 160 160-56 56-64-64v7q0 100 70.5 170T482-240q26 0 51-6t49-18l60 60q-38 22-78 33t-82 11Zm278-161L600-481l56-56 64 64v-7q0-100-70.5-170T478-720q-26 0-51 6t-49 18l-60-60q38-22 78-33t82-11q134 0 228 93t94 227v7l64-64 56 56-160 160Z" /> - From 67ee8617d25465134afbdd1abcdaf9fe7d03c366 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Sat, 29 Jun 2024 14:26:31 +0300 Subject: [PATCH 19/21] alternative coords location --- src/api/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/parser.js b/src/api/parser.js index db553c7..35a1ac3 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -226,7 +226,7 @@ export default function parser(data, rpcid) { savedToYourPhotos: rawItemData[0]?.[12].filter((subArray) => subArray.includes(20)).length === 0, // owner: owner, geoLocation: { - coordinates: rawItemData[0]?.[13]?.[0], + coordinates: rawItemData[0]?.[9]?.[0] || rawItemData[0]?.[13]?.[0], name: rawItemData[0]?.[13]?.[2]?.[0]?.[1]?.[0]?.[0], mapThumb: rawItemData?.[1], }, From 53bd95a7a1ea66e2427fe06ce0124229d797497d Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Sat, 29 Jun 2024 14:29:07 +0300 Subject: [PATCH 20/21] new notes --- src/api/parser.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/api/parser.js b/src/api/parser.js index 35a1ac3..d1ee9b5 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -1,7 +1,14 @@ export default function parser(data, rpcid) { - // note - // add =w417-h174-k-no?authuser=0 to thumbnail url to - // set custon size, remove 'video' watermark, remove auth requirement + + /* notes + + add =w417-h174-k-no?authuser=0 to thumbnail url to set custon size, remove 'video' watermark, remove auth requirement + + dedup key is a base64 encoded SHA1 of a the file + + */ + + function libraryItemParse(rawItemData) { return { From 84d68df6dac4d148ca740b261d52fdd56d2a7966 Mon Sep 17 00:00:00 2001 From: xob0t <32616886+xob0t@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:17:49 +0300 Subject: [PATCH 21/21] parser: `other` value --- src/api/parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/parser.js b/src/api/parser.js index d1ee9b5..be0b7c6 100644 --- a/src/api/parser.js +++ b/src/api/parser.js @@ -237,6 +237,7 @@ export default function parser(data, rpcid) { name: rawItemData[0]?.[13]?.[2]?.[0]?.[1]?.[0]?.[0], mapThumb: rawItemData?.[1], }, + other: rawItemData[0]?.[31], }; }