bananalyzer
Version:
A simple tool for windows 10, that downloads APKs from Google playstore, analyzes them, and lists all the Google and Huawei SDKs (kits) that are integrated, along with other metadata
333 lines (332 loc) • 18.4 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDownloadLink = exports.downloadAPK = void 0;
/**
*
* it download apps from playstore using puppeteer and the website https://apps.evozi.com/apk-downloader
* (this script is identical to ./downloader.ts, except it uses a different website which i found faster and more reliable)
*/
var debug_1 = __importDefault(require("debug"));
var fs_1 = __importDefault(require("fs"));
var path_1 = __importDefault(require("path"));
var consts_1 = require("../consts");
var storeInfo_1 = require("../models/storeInfo");
var BrowserManager_1 = __importDefault(require("./BrowserManager"));
var utils_1 = require("./utils");
var debug = (0, debug_1.default)('bananalyzer:downloader');
// max number of attempts of downloading an apk from source 2, there is one minute delay between each attempt
var MAX_ATTEMPTS_COUNT = 2;
// block ads and tracker to speed page loading
// puppeteer.use(adblockerPlugin({ blockTrackers: true }));
// trying to hide that we are using a headless browser
//fixme: causes errors when building .exe
// puppeteer.use(stealthPlugin());
// download from https://apk.support/download-app
var getDownloadLink1 = function (page, packageName, mergeSplitApk) {
return new Promise(function (resolve, reject) { return __awaiter(void 0, void 0, void 0, function () {
var link, e_1, maybeError, maybeErrorLowerCase, storeInfo, mergeApkButtonSelector, splitApkEl, apkLinkSelector, apkDownloadLink, apkSizeSelector, apkDownloadSize, firstLinkSelector, firstLinkHref, firstApkSizeSelector, _a, versionNameSelector, versionNameText, versionRegex, versionNameMatchRes, uploadDateSelector, uploadDateText, uploadDateRegex, uploadDateMatchRes, e_2;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 23, , 24]);
debug('getDownloadLink1 => ' + packageName);
link = 'https://apk.support/apk-downloader';
debug('using link ' + link);
return [4 /*yield*/, page.goto(link, { waitUntil: 'networkidle0' })];
case 1:
_b.sent();
return [4 /*yield*/, page.waitForSelector('input[name=package]')];
case 2:
_b.sent();
debug('page loaded');
return [4 /*yield*/, page.type('input[name=package]', packageName, { delay: 30 })];
case 3:
_b.sent();
debug('set package name');
return [4 /*yield*/, page.click('#apksubmit')];
case 4:
_b.sent();
_b.label = 5;
case 5:
_b.trys.push([5, 7, , 8]);
// wait for download link or error, can take up to 30 seconds
return [4 /*yield*/, page.waitForSelector('#downloader_area > .appinfo,#downloader_area > br', { timeout: 30 * 1000 })];
case 6:
// wait for download link or error, can take up to 30 seconds
_b.sent();
return [3 /*break*/, 8];
case 7:
e_1 = _b.sent();
return [2 /*return*/, reject('Timeout error while looking for the app')];
case 8:
debug('page finished fetching apk info');
return [4 /*yield*/, BrowserManager_1.default.getTextContent(page, '#downloader_area')];
case 9:
maybeError = _b.sent();
maybeErrorLowerCase = maybeError.toLowerCase();
if (!(maybeErrorLowerCase.indexOf('not found or invalid') != -1 ||
maybeErrorLowerCase.indexOf("removed from the play store") != -1)) return [3 /*break*/, 11];
debug('got error: ', maybeErrorLowerCase);
return [4 /*yield*/, (0, utils_1.delay)(2000)
// we have an error
];
case 10:
_b.sent();
// we have an error
return [2 /*return*/, reject(new Error('the requested app is not found or invalid'))];
case 11:
storeInfo = {
packageName: packageName,
source: storeInfo_1.ApkSource.APK_SUPPORT,
};
mergeApkButtonSelector = '#zipapk';
return [4 /*yield*/, page.$(mergeApkButtonSelector)];
case 12:
splitApkEl = _b.sent();
if (!(!!splitApkEl && mergeSplitApk)) return [3 /*break*/, 17];
return [4 /*yield*/, page.click(mergeApkButtonSelector)];
case 13:
_b.sent();
apkLinkSelector = '#zip_area > a';
return [4 /*yield*/, page.waitForSelector(apkLinkSelector, { timeout: 60 * 1000 })];
case 14:
_b.sent();
return [4 /*yield*/, BrowserManager_1.default.getHref(page, apkLinkSelector)];
case 15:
apkDownloadLink = _b.sent();
apkSizeSelector = '#zip_area > a > span:nth-child(3)';
return [4 /*yield*/, BrowserManager_1.default.getTextContent(page, apkSizeSelector)];
case 16:
apkDownloadSize = _b.sent();
storeInfo.downloadLink = apkDownloadLink;
storeInfo.apkSize = apkDownloadSize;
return [3 /*break*/, 20];
case 17:
firstLinkSelector = '#sse > div:nth-child(1) > a';
return [4 /*yield*/, BrowserManager_1.default.getHref(page, firstLinkSelector)];
case 18:
firstLinkHref = _b.sent();
storeInfo.downloadLink = firstLinkHref;
firstApkSizeSelector = '#sse > div:nth-child(1) > a > span.der_size';
_a = storeInfo;
return [4 /*yield*/, BrowserManager_1.default.getTextContent(page, firstApkSizeSelector)];
case 19:
_a.apkSize = _b.sent();
_b.label = 20;
case 20:
versionNameSelector = '#downloader_area > div.jinfo > ul > li:nth-child(5)';
return [4 /*yield*/, BrowserManager_1.default.getTextContent(page, versionNameSelector)];
case 21:
versionNameText = _b.sent();
if (!!versionNameText) {
versionRegex = /\"(Version Name|VersionName)\": \"(.*)\"/;
versionNameMatchRes = versionNameText.match(versionRegex);
if (!!versionNameMatchRes && versionNameMatchRes.length > 1)
storeInfo.versionName = versionNameMatchRes[2];
debug('version from website is ', storeInfo.versionName);
}
uploadDateSelector = '#downloader_area > div.jinfo > ul > li:nth-child(7)';
return [4 /*yield*/, BrowserManager_1.default.getTextContent(page, uploadDateSelector)];
case 22:
uploadDateText = _b.sent();
if (!!uploadDateText) {
uploadDateRegex = /(Updated on|UploadDate)\"\: \"(.*)\"/;
uploadDateMatchRes = uploadDateText.match(uploadDateRegex);
if (!!uploadDateMatchRes && uploadDateMatchRes.length > 1)
storeInfo.uploadDate = uploadDateMatchRes[2];
}
// let hrefs = await page.$('.down_b_area .browser a')
// storeInfo.downloadLink = await page.$eval('.dvContents a', (el: any) => {
// return el.href;
// });
resolve(storeInfo);
return [3 /*break*/, 24];
case 23:
e_2 = _b.sent();
debug(e_2);
reject(new Error('failed to get download link'));
return [3 /*break*/, 24];
case 24: return [2 /*return*/];
}
});
}); });
};
/**
* this function will try to download an apk for app represted by @param packageName,
* sometimes we may get rate limited, therefore this function will call it self (recursive call) again if it detects rate limit, one every minute, 10 times max
* @param {string} packageName the package name of the app
* @param {number} attempt attempt number
*
* @return {Promise}
*/
var downloadAPK = function (packageName, useExisting, mergeSplitApk, closeBrowser) {
if (mergeSplitApk === void 0) { mergeSplitApk = true; }
if (closeBrowser === void 0) { closeBrowser = false; }
return new Promise(function (resolve, reject) { return __awaiter(void 0, void 0, void 0, function () {
var filePath, downloadData, downloadLink, versionName, apkSize, uploadDate, source, e_3, e_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
debug('download APK → ' + packageName);
// create download folder if missing
try {
if (!fs_1.default.existsSync(consts_1.DOWNLOAD_FOLDER))
fs_1.default.mkdirSync(consts_1.DOWNLOAD_FOLDER);
}
catch (e) {
console.log("Bruhhh I coulnd't mkdir a folder!!!");
reject(new Error(e));
return [2 /*return*/];
}
filePath = path_1.default.join(consts_1.DOWNLOAD_FOLDER, packageName + '.apk');
_a.label = 1;
case 1:
_a.trys.push([1, 7, , 8]);
if (fs_1.default.existsSync(filePath)) {
if (useExisting)
console.log("using existing apk: ".concat(packageName, " \u2192 ").concat(filePath));
else
console.log("apk will be overwritten: ".concat(packageName, " \u2192 ").concat(filePath));
if (useExisting) {
resolve({ packageName: packageName, filePath: filePath });
return [2 /*return*/];
}
}
return [4 /*yield*/, (0, exports.getDownloadLink)(packageName, mergeSplitApk, closeBrowser)];
case 2:
downloadData = _a.sent();
if (!downloadData || !downloadData.downloadLink) {
throw Error('failed to get download link (requested app is not found or invalid)');
}
downloadLink = downloadData.downloadLink, versionName = downloadData.versionName, apkSize = downloadData.apkSize, uploadDate = downloadData.uploadDate, source = downloadData.source;
debug(" uplaodDate = ".concat(uploadDate, " , source=").concat(source, " "));
debug('file path: ' + filePath);
debug('download link: ' + downloadLink);
// todo: remove
// if (fs.existsSync(filePath)) {
// if (useExisting) console.log(`using existing apk: ${packageName} → ${filePath}`);
// else console.log(`apk will be overwritten: ${packageName} → ${filePath}`);
// if (useExisting) {
// resolve({ packageName, filePath, uploadDate });
// return;
// }
// }
console.log("\u2192 Downloading ".concat(packageName, " \u2192 version= ").concat(versionName, ", download size = ").concat(apkSize ? apkSize : '? Mb', " "));
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, (0, utils_1.downloadFileGot)(downloadLink, filePath)];
case 4:
_a.sent();
return [3 /*break*/, 6];
case 5:
e_3 = _a.sent();
return [2 /*return*/, reject(e_3)];
case 6:
console.log('✓ APK file is ready → ' + packageName);
resolve({ packageName: packageName, filePath: filePath, uploadDate: uploadDate, versionName: versionName, size: apkSize });
debug('Downlading APK file started ==>> ' + packageName);
return [3 /*break*/, 8];
case 7:
e_4 = _a.sent();
debug(e_4);
debug('APK file not found → ' + packageName);
reject(e_4);
return [3 /*break*/, 8];
case 8: return [2 /*return*/];
}
});
}); });
};
exports.downloadAPK = downloadAPK;
var getDownloadLink = function (packageName, mergeSplitApk, closeBrowserWhenDone) {
if (mergeSplitApk === void 0) { mergeSplitApk = true; }
if (closeBrowserWhenDone === void 0) { closeBrowserWhenDone = false; }
return __awaiter(void 0, void 0, void 0, function () {
var page, downloadData, e1_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, BrowserManager_1.default.getChromiumPage()];
case 1:
page = _a.sent();
console.log('Searching for APK File → ' + packageName, '(can take up to 3 minutes!)');
_a.label = 2;
case 2:
_a.trys.push([2, 8, , 13]);
return [4 /*yield*/, getDownloadLink1(page, packageName, mergeSplitApk)];
case 3:
// try from source 1 (1 attempt)
downloadData = _a.sent();
if (!!!page) return [3 /*break*/, 5];
return [4 /*yield*/, page.close()];
case 4:
_a.sent();
_a.label = 5;
case 5:
if (!closeBrowserWhenDone) return [3 /*break*/, 7];
return [4 /*yield*/, BrowserManager_1.default.closeBrowser()];
case 6:
_a.sent();
_a.label = 7;
case 7: return [2 /*return*/, downloadData];
case 8:
e1_1 = _a.sent();
debug(e1_1);
debug('failed to get download link from source1');
if (!!!page) return [3 /*break*/, 10];
return [4 /*yield*/, page.close()];
case 9:
_a.sent();
_a.label = 10;
case 10:
if (!closeBrowserWhenDone) return [3 /*break*/, 12];
return [4 /*yield*/, BrowserManager_1.default.closeBrowser()];
case 11:
_a.sent();
_a.label = 12;
case 12: return [2 /*return*/, undefined];
case 13: return [2 /*return*/];
}
});
});
};
exports.getDownloadLink = getDownloadLink;