diff --git a/classes/API.js b/classes/API.js index a0f0da6..e6c6030 100644 --- a/classes/API.js +++ b/classes/API.js @@ -3,6 +3,8 @@ import { wrapper } from 'axios-cookiejar-support'; import { CookieJar } from 'tough-cookie'; import axiosModule from 'axios'; import { LinkedInProfile, Company, GenericEntity, Group, SocialActivityCounts } from '../index.js'; +import { LoadingBar } from './misc.js'; + const cookieJar = new CookieJar(); const axios = wrapper(axiosModule.create({ @@ -187,21 +189,28 @@ export default class linkedInAPIClass { */ evade = () => wait(randomIntFromInterval(2, 5) * 1000); - /** - * @param {String} keyword i.e. "biotechnology" - * @param {Array?} numEmp - * @param {Number?} [limit=1000] the function will use the bound the given number is contained in (see {@link findRangeIndex} for ranges) - * @param {Number?} [start=0] - * @param {boolean?} [castToClass=true] whether the function should return a list of Company classes or just raw JSON - * @param {boolean} [excludeGeneric=false] - * @returns {Promise<[SocialActivityCounts | Group | Company | GenericEntity]>} - */ - async searchCompanies(keyword, numEmp = undefined, limit = 1000, start = 0, castToClass = true, excludeGeneric = false) { + /** + * @param {String} keyword i.e. "biotechnology" + * @param {Array?} numEmp + * @param {Number?} [limit=1000] the function will use the bound the given number is contained in (see {@link findRangeIndex} for ranges) + * @param {Number?} [start=0] + * @param {boolean?} [castToClass=true] whether the function should return a list of Company classes or just raw JSON + * @param {boolean} [excludeGeneric=false] + * @returns {Promise<[SocialActivityCounts | Group | Company | GenericEntity]>} + */ + async searchCompanies(keyword, numEmp = undefined, limit = 1000, start = 0, castToClass = true, excludeGeneric = false) { const compAll = []; + + if (this.logAll) console.log(`scanning for ${limit} companies with the keyword "${keyword}"`); + + const lb = (this.logAll) ? new LoadingBar(Math.round(limit / 50)) : null; + for (let i = start; i < limit; i += 50) { let urlExt = `variables=(start:${i},origin:GLOBAL_SEARCH_HEADER,query:(keywords:${keyword},flagshipSearchIntent:SEARCH_SRP,queryParameters:List((key:resultType,value:List(COMPANIES))${(numEmp) ? `,(key:companySize,value:List(${numsToSizes(...numEmp)}))` : ''}),includeFiltersInResponse:false))`; const r = await this._makeReq(urlExt); + if (this.logAll) lb.increment(); + if (!r?.included && r?.data?.errors) { console.error(JSON.stringify(r.data.errors)) throw "ERROR!"; @@ -230,7 +239,9 @@ export default class linkedInAPIClass { * LAPI.searchEmployees("John Appleseed", 50, true, ["1418841"], [1]) */ async searchEmployees(keyword, limit = 1000, castToClass = true, filterObfuscated = true, currentCompanies = [], conDeg = []) { - const empAll = []; + const empAll = [], + lb = (this.logAll) ? new LoadingBar(Math.floor(limit/10)) : null; + for (let i = 0; empAll.length < limit; i += 50) { let urlExt = `includeWebMetadata=true&variables=(start:${i},query:(keywords:${keyword},flagshipSearchIntent:SEARCH_SRP,queryParameters:List((key:resultType,value:List(PEOPLE))`; @@ -239,6 +250,8 @@ export default class linkedInAPIClass { urlExt += ')))'; const r = await this._makeReq(urlExt); + + if (this.logAll) lb.increment(5); // there's nothing left, returns what we have if (!r?.included?.length) { @@ -281,15 +294,17 @@ export default class linkedInAPIClass { let cookie; if (this.resetCookies || !fs.existsSync("cookie.txt")) { cookie = await this.helper.getCookies(username, password); - console.log(cookie); + if (this.logAll) console.log(cookie); - if (cookie['login_result'] != "CHALLENGE") throw `\n\nIP IS NOT AUTHORIZED, PLEASE LOG IN THROUGH YOUR BROWSER USING:\nusername: ${username}\npassword: ${password}\n\n`; + if (cookie['login_result'] == "CHALLENGE") throw `\n\nIP IS NOT AUTHORIZED, PLEASE LOG IN THROUGH YOUR BROWSER USING:\nusername: ${username}\npassword: ${password}\n\n`; if (cookie['login_result'] != "PASS") throw cookie; // const sessioncookie = await this.#getCookies(); // throw "NO COOKIE FOUND!"; } else cookie = fs.readFileSync('cookie.txt'); + if (this.logAll) console.log("LOGGED IN!"); + this.headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0', 'Accept': 'application/vnd.linkedin.normalized+json+2.1', @@ -311,7 +326,8 @@ export default class linkedInAPIClass { }; } - constructor(resetCookies = false) { + constructor(logAll = false, resetCookies = false) { + this.logAll = logAll; this.resetCookies = resetCookies; this.helper = new APIHelper(); } diff --git a/classes/misc.js b/classes/misc.js index 0197e5d..89d05c1 100644 --- a/classes/misc.js +++ b/classes/misc.js @@ -1,3 +1,6 @@ +import { cursorTo } from "readline"; + + export class ReactionTypeCount { constructor({ count, reactionType }) { this.count = count; @@ -27,4 +30,27 @@ export class GenericEntity { constructor(data) { Object.assign(this, data); } +} + + +export class LoadingBar { + constructor(size) { + this.size = size; + this.cursor = 0; + this.timer = null; + + cursorTo(process.stdout, this.cursor); + + // draw the initial outline + process.stdout.write("\x1B[?25l"); + for (let i = 0; i < this.size; i++) { + process.stdout.write("\u2591"); + } + } + + increment(amt) { + cursorTo(process.stdout, this.cursor); + for (let i = 0; i < amt; i++) process.stdout.write("\u2588"); + this.cursor += amt; + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e18ffb2..382e06d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { - "name": "linkedin-api", - "version": "1.0.0", + "name": "linkedin-api-js", + "version": "1.0.0-11", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "linkedin-api", - "version": "1.0.0", + "name": "linkedin-api-js", + "version": "1.0.0-11", "license": "ISC", "dependencies": { "axios": "^1.6.8", "axios-cookiejar-support": "^5.0.1", "cheerio": "^1.0.0-rc.12", + "readline": "^1.3.0", "tough-cookie": "^4.1.3" } }, @@ -381,6 +382,11 @@ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", diff --git a/package.json b/package.json index 97ac742..6a0761f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "axios": "^1.6.8", "axios-cookiejar-support": "^5.0.1", "cheerio": "^1.0.0-rc.12", + "readline": "^1.3.0", "tough-cookie": "^4.1.3" }, "type": "module", diff --git a/tests/companyTests.js b/tests/companyTests.js index 600d6f6..ab09ae4 100644 --- a/tests/companyTests.js +++ b/tests/companyTests.js @@ -2,7 +2,7 @@ import LinkedInAPIClass from "../index.js"; import fs from 'fs'; (async () => { - const LAPI = new LinkedInAPIClass(); + const LAPI = new LinkedInAPIClass(true); const o = JSON.parse(fs.readFileSync('config.json')); await LAPI.login(o.email, o.password);