UNPKG

instamancer

Version:

Scrape the Instagram API with Puppeteer

916 lines 64.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Instagram = void 0; const await_lock_1 = __importDefault(require("await-lock")); const chalk_1 = __importDefault(require("chalk")); const Either_1 = require("fp-ts/lib/Either"); const PathReporter_1 = require("io-ts/lib/PathReporter"); const ThrowReporter_1 = require("io-ts/lib/ThrowReporter"); const _ = __importStar(require("lodash/object")); const puppeteer_1 = require("puppeteer"); const winston = __importStar(require("winston")); const plugins_1 = require("../../plugins"); const postIdSet_1 = require("./postIdSet"); /** * Instagram API wrapper */ class Instagram { /** * Create API wrapper instance * @param endpoint the url for the type of resource to scrape * @param id the identifier for the resource * @param pageQuery the query to identify future pages in the nested API structure * @param edgeQuery the query to identify posts in the nested API structure * @param options configuration details * @param validator response type validator */ constructor(endpoint, id, pageQuery, edgeQuery, options = {}, validator) { // Iteration state this.started = false; this.paused = false; this.finished = false; // Instagram URLs this.catchURL = "https://www.instagram.com/graphql/query"; this.postURL = "https://www.instagram.com/p/"; this.defaultPostURL = "https://www.instagram.com/p/"; // Number of jumps before grafting this.jumpMod = 100; // Depth of jumps this.jumpSize = 2; // Implementation-specific page functions this.defaultPageFunctions = []; // Validations this.strict = false; this.browserDisconnected = true; // Array of scraped posts and lock this.postBuffer = []; this.postBufferLock = new await_lock_1.default(); // Request and Response buffers and locks this.requestBuffer = []; this.requestBufferLock = new await_lock_1.default(); this.responseBuffer = []; this.responseBufferLock = new await_lock_1.default(); // Get full amount of data from API this.fullAPI = false; this.pagePromises = []; // Grafting state this.enableGrafting = true; this.sameBrowser = false; this.graft = false; this.graftURL = null; this.graftHeaders = null; this.foundGraft = false; // Hibernation due to rate limiting this.hibernate = false; this.hibernationTime = 60 * 20; // 20 minutes // Number of jumps before exiting because lack of data this.failedJumps = 20; this.responseFromAPI = false; this.index = 0; this.jumps = 0; // Number of times to attempt to visit url initially this.maxPageUrlAttempts = 3; this.pageUrlAttempts = 0; this.postPageRetries = 5; // Output this.silent = false; this.writeLock = new await_lock_1.default(); // Sleep time remaining this.sleepRemaining = 0; // Length of time to sleep for this.sleepTime = 2; // Plugins to be run this.pluginFunctions = { browser: [], construction: [], grafting: [], postPage: [], request: [], response: [], }; this.id = id; this.postIds = new postIdSet_1.PostIdSet(); this.url = endpoint.replace("[id]", id); options = Instagram.defaultOptions(options); this.total = options.total; this.pageQuery = pageQuery; this.edgeQuery = edgeQuery; this.browserInstance = options.browserInstance; this.headless = options.headless; this.logger = options.logger; this.silent = options.silent; this.strict = options.strict; this.enableGrafting = options.enableGrafting; this.sameBrowser = options.sameBrowser; this.sleepTime = options.sleepTime; this.hibernationTime = options.hibernationTime; this.fullAPI = options.fullAPI; this.proxyURL = options.proxyURL; this.executablePath = options.executablePath; this.validator = options.validator || validator; this.addPlugins(options["plugins"]); this.executePlugins("construction"); } /** * Apply defaults to undefined options */ static defaultOptions(options) { if (options.enableGrafting === undefined) { options.enableGrafting = true; } if (options.sameBrowser === undefined) { options.sameBrowser = false; } if (options.fullAPI === undefined) { options.fullAPI = false; } if (options.headless === undefined) { options.headless = true; } if (options.logger === undefined) { options.logger = winston.createLogger({ silent: true, }); } if (options.silent === undefined) { options.silent = true; } if (options.sleepTime === undefined) { options.sleepTime = 2; } if (options.hibernationTime === undefined) { options.hibernationTime = 60 * 20; } if (options.total === undefined) { options.total = 0; } return options; } /** * Toggle pausing data collection */ pause() { this.paused = !this.paused; } /** * Toggle prolonged pausing */ toggleHibernation() { this.hibernate = true; } /** * Force the API to stop */ async forceStop(force) { if (!force && !this.started) { return; } this.started = false; this.finish(FinishedReasons.FORCED_STOP); try { this.requestBufferLock.release(); // tslint:disable-next-line: no-empty } catch (e) { } try { this.responseBufferLock.release(); // tslint:disable-next-line: no-empty } catch (e) { } await this.stop(); } /** * Generator of posts on page */ async *generator() { // Start if haven't done so already if (!this.started) { await this.start(); } while (true) { // Get more posts await this.getNext(); // Yield posts from buffer let post = await this.postPop(); while (post) { yield post; post = await this.postPop(); } // End loop when finished, check for pagePromises if fullAPI if (this.finished && this.pagePromises.length === 0) { break; } } await this.stop(); // Add newline to end of output if (!this.silent) { process.stdout.write("\n"); } } /** * Construct page and add listeners */ async start() { let pageConstructed; this.pageUrlAttempts = 0; while (this.pageUrlAttempts++ < this.maxPageUrlAttempts) { pageConstructed = await this.constructPage(); if (pageConstructed) { break; } } if (!pageConstructed) { await this.forceStop(true); throw new Error("Failed to visit URL"); } // Build page and visit url await this.executePlugins("browser"); this.started = true; // Add event listeners for requests and responses await this.page.setRequestInterception(true); this.page.on("request", (req) => this.interceptRequest(req)); this.page.on("response", (res) => this.interceptResponse(res)); this.page.on("requestfailed", (res) => this.interceptFailure(res)); this.page.on("console", (message) => this.logger.info("Console log", { message })); // Ignore dialog boxes this.page.on("dialog", (dialog) => dialog.dismiss()); // Log errors /* istanbul ignore next */ this.page.on("error", (error) => this.logger.error("Console error", { error })); // Gather initial posts from web page if (this.fullAPI) { await this.scrapeDefaultPosts(); } } /** * Match the url to the url used in API requests */ matchURL(url) { return url.startsWith(this.catchURL) && !url.includes("include_reel"); } /** * Close the page and browser */ async stop() { await this.progress(Progress.CLOSING); // Remove listeners if (!this.page.isClosed()) { this.page.removeAllListeners("request"); this.page.removeAllListeners("response"); this.page.removeAllListeners("requestfailed"); } // Clear request buffers await this.requestBufferLock.acquireAsync(); this.requestBuffer = []; this.requestBufferLock.release(); // Clear response buffers await this.responseBufferLock.acquireAsync(); this.responseBuffer = []; this.responseBufferLock.release(); // Close page if (!this.page.isClosed()) { await this.page.close(); } if (this.finished && !this.browserDisconnected && !this.browserInstance) { await this.browser.close(); } } /** * Finish retrieving data for the generator */ finish(reason) { this.finished = true; this.finishedReason = reason; this.logger.info("Finished collecting", { reason }); } /** * Process the requests in the request buffer */ async processRequests() { await this.requestBufferLock.acquireAsync(); let newApiRequest = false; for (const req of this.requestBuffer) { // Match url if (!this.matchURL(req.url())) { continue; } else { newApiRequest = true; } // Begin grafting if required, else continue the request if (this.graft) { if (this.foundGraft === false) { // Gather details this.graftURL = req.url(); this.graftHeaders = req.headers(); this.foundGraft = true; // Cancel request await req.abort(); } else { // Swap request const overrides = { headers: this.graftHeaders, url: this.graftURL, }; await this.executePlugins("request", req, overrides); await req.continue(overrides); // Reset grafting data this.graft = false; this.foundGraft = false; this.graftURL = null; this.graftHeaders = null; } // Stop reading requests break; } else { const overrides = {}; this.executePlugins("request", req, overrides); await req.continue(overrides); } } // Clear buffer and release this.requestBuffer = []; this.requestBufferLock.release(); if (this.foundGraft && newApiRequest) { // Restart browser and page, clearing all buffers await this.stop(); await this.start(); } } /** * Process the responses in the response buffer */ async processResponses() { await this.responseBufferLock.acquireAsync(); for (const res of this.responseBuffer) { // Match url if (!this.matchURL(res.url())) { continue; } // Acknowledge receipt of response this.responseFromAPI = true; // Get JSON data let data; try { data = await res.json(); if (typeof data !== "object") { this.logger.error("Response data is not an object", { data }); continue; } } catch (error) { this.logger.error("Error processing response JSON", { data, error, }); continue; } // Emit event this.executePlugins("response", res, data); // Check for rate limiting if (data && "status" in data && data["status"] === "fail") { this.logger.info("Rate limited"); this.hibernate = true; continue; } // Check for next page if (!(_.get(data, this.pageQuery + ".has_next_page", false) && _.get(data, this.pageQuery + ".end_cursor", false))) { this.logger.info("No posts remaining", { data }); this.finish(FinishedReasons.API_FINISHED); } await this.processResponseData(data); } // Clear buffer and release this.responseBuffer = []; this.responseBufferLock.release(); } async processResponseData(data) { // Get posts const posts = _.get(data, this.edgeQuery, []); for (const post of posts) { const postId = post["node"]["id"]; // Check it hasn't already been cached const contains = this.postIds.add(postId); if (contains) { this.logger.info("Duplicate id found", { postId }); continue; } // Add to postBuffer if (this.index < this.total || this.total === 0) { this.index++; if (this.fullAPI) { this.pagePromises.push(this.postPage(post["node"]["shortcode"], this.postPageRetries)); } else { await this.addToPostBuffer(post); } } else { this.finish(FinishedReasons.TOTAL_REACHED_API); break; } } } /** * Open a post in a new page, then extract its metadata */ async postPage(post, retries) { // Create page const postPage = await this.browser.newPage(); await postPage.setRequestInterception(true); postPage.on("request", async (req) => { if (!req.url().includes("/p/" + post)) { await req.abort(); } else { await req.continue(); } }); postPage.on("requestfailed", async (req) => this.interceptFailure(req)); // Visit post and read state let parsed; try { await postPage.goto(this.postURL + post + "/"); } catch (error) { await this.handlePostPageError(postPage, error, "Couldn't navigate to page", post, retries); return; } // Load data from memory let data; try { /* istanbul ignore next */ data = await postPage.evaluate(async () => { // Wait for _sharedData value to be set await new Promise((resolve) => { let i = 0; const findSharedData = setInterval(() => { if (window["_sharedData"] !== undefined || i++ > 5) { resolve(); clearInterval(findSharedData); } }, 2000); }); return JSON.stringify(window["_sharedData"].entry_data.PostPage[0].graphql); }); } catch (error) /* istanbul ignore next */ { await this.handlePostPageError(postPage, error, "Couldn't evaluate on page", post, retries); return; } // Close page await postPage.close(); // Parse data to PostType try { parsed = JSON.parse(data); } catch (error) /* istanbul ignore next */ { await this.handlePostPageError(postPage, error, "Couldn't parse page data", post, retries); return; } await this.executePlugins("postPage", parsed); await this.addToPostBuffer(parsed); } async handlePostPageError(page, error, message, post, retries) { // Log error and wait this.logger.error(message, { error }); await this.progress(Progress.ABORTED); await this.sleep(2); // Close existing attempt if (!page.isClosed()) { await page.close(); } // Retry if (retries > 0) { await this.postPage(post, --retries); } } async validatePost(post) { const validationResult = this.validator.decode(post); if (this.strict) { try { ThrowReporter_1.ThrowReporter.report(validationResult); } catch (e) { await this.forceStop(); throw e; } return; } if (Either_1.isLeft(validationResult)) { const validationReporter = PathReporter_1.PathReporter.report(validationResult); this.logger.warn(` Warning! The Instagram API has been changed since this version of instamancer was released. More info: https://scriptsmith.github.io/instamancer/api-change `, { validationReporter, post }); } } /** * Stimulate the page until responses gathered */ async getNext() { await this.progress(Progress.SCRAPING); while (true) { // Process results (if any) await this.processRequests(); await this.processResponses(); // Finish page promises if (this.pagePromises.length > 0) { await this.progress(Progress.BRANCHING); await Promise.all(this.pagePromises); this.pagePromises = []; } // Check if finished if (this.finished) { break; } // Pause if paused await this.waitResume(); // Interact with page to stimulate request await this.jump(); // Stop if no data is being gathered if (this.jumps === this.failedJumps) { if (this.fullAPI) { if (!this.responseFromAPI) { this.finish(FinishedReasons.NO_RESPONSE); } } else if (this.index === 0) { this.finish(FinishedReasons.NO_INCREMENT); const pageContent = { content: "" }; try { pageContent.content = await this.page.content(); } catch (e) { // No content } this.logger.error("Page failed to make requests", pageContent); break; } } // Enable grafting if required if (this.jumps % this.jumpMod === 0) { await this.initiateGraft(); } // Sleep await this.sleep(this.sleepTime); // Hibernate if rate-limited if (this.hibernate) { await this.sleep(this.hibernationTime); this.hibernate = false; } // Break if posts in buffer await this.postBufferLock.acquireAsync(); const posts = this.postBuffer.length; this.postBufferLock.release(); if (posts > 0) { break; } } } /** * Halt execution * @param time Seconds */ async sleep(time) { for (let i = time; i > 0; i--) { this.sleepRemaining = i; await this.progress(Progress.SCRAPING); await new Promise((resolve) => { setTimeout(resolve, i >= 1 ? 1000 : i * 1000); }); } this.sleepRemaining = 0; await this.progress(Progress.SCRAPING); } /** * Create the browser and page, then visit the url */ async constructPage() { // Browser args const args = []; /* istanbul ignore if */ if (process.env.NO_SANDBOX) { args.push("--no-sandbox"); args.push("--disable-setuid-sandbox"); } if (this.proxyURL !== undefined) { args.push("--proxy-server=" + this.proxyURL); } // Browser launch options const options = { args, headless: this.headless, }; if (this.executablePath !== undefined) { options.executablePath = this.executablePath; } // Launch browser if (this.browserInstance) { await this.progress(Progress.LAUNCHING); this.browser = this.browserInstance; this.browserDisconnected = !this.browser.isConnected(); this.browser.on("disconnected", () => (this.browserDisconnected = true)); } else if (!this.sameBrowser || (this.sameBrowser && !this.started)) { await this.progress(Progress.LAUNCHING); this.browser = await puppeteer_1.launch(options); this.browserDisconnected = false; this.browser.on("disconnected", () => (this.browserDisconnected = true)); } // New page this.page = await this.browser.newPage(); await this.progress(Progress.OPENING); // Attempt to visit URL try { await this.page.goto(this.url); // Check page loads /* istanbul ignore next */ const pageLoaded = await this.page.evaluate(() => { const headings = document.querySelectorAll("h2"); for (const heading of Array.from(headings)) { if (heading.innerHTML === "Sorry, this page isn't available.") { return false; } } return true; }); if (!pageLoaded) { await this.handleConstructionError("Page loaded with no content", 10); return false; } // Run defaultPagePlugins for (const f of this.defaultPageFunctions) { await this.page.evaluate(f); } // Fix issue with disabled scrolling /* istanbul ignore next */ await this.page.evaluate(() => { setInterval(() => { try { document.body.style.overflow = ""; } catch (error) { this.logger.error("Failed to update style", { error }); } }, 10000); }); } catch (e) { await this.handleConstructionError(e, 60); return false; } return true; } /*** * Handle errors that occur during page construction */ async handleConstructionError(error, timeout) { // Log error and wait this.logger.error("Construction error", { error, url: this.url }); await this.progress(Progress.ABORTED); await this.sleep(timeout); // Close existing attempt if (!this.page.isClosed()) { await this.page.close(); } await this.browser.close(); } /** * Pause and wait until resumed */ async waitResume() { // Pause for 200 milliseconds function f() { return new Promise((resolve) => { setTimeout(resolve, 200); }); } // Pause until pause toggled while (this.paused === true) { await this.progress(Progress.PAUSED); await f(); } } /** * Pop a post off the postBuffer (using locks). Returns null if no posts in buffer */ async postPop() { let post = null; await this.postBufferLock.acquireAsync(); if (this.postBuffer.length > 0) { post = this.postBuffer.shift(); } this.postBufferLock.release(); return post; } /** * Print progress to stderr */ async progress(state) { // End if silent if (this.silent) { return; } // Lock await this.writeLock.acquireAsync(); // Calculate total const total = this.total === 0 ? "Unlimited" : this.total; // Generate output string const idStr = chalk_1.default.bgYellow.black(` ${this.id} `); const totalStr = chalk_1.default.bgBlack(` Total: ${total} `); const stateStr = chalk_1.default.bgWhite.black(` State: ${state} `); const sleepStr = chalk_1.default.bgWhite.black(` Sleeping: ${this.sleepRemaining} `); const indexStr = chalk_1.default.bgWhite.black(` Scraped: ${this.index} `); this.logger.debug({ id: this.id, index: this.index, sleepRemaining: this.sleepRemaining, state, total, }); // Print output process.stderr.write(`\r${idStr}${totalStr}${stateStr}${sleepStr}${indexStr}\u001B[K`); // Release this.writeLock.release(); } /** * Add request to the request buffer */ async interceptRequest(req) { await this.requestBufferLock.acquireAsync(); this.requestBuffer.push(req); await this.requestBufferLock.release(); } /** * Add the response to the response buffer */ async interceptResponse(res) { await this.responseBufferLock.acquireAsync(); this.responseBuffer.push(res); await this.responseBufferLock.release(); } /** * Log failed requests */ async interceptFailure(req) { this.logger.info("Failed request", { url: req.url() }); await this.progress(Progress.ABORTED); } /** * Add post to buffer */ async addToPostBuffer(post) { await this.postBufferLock.acquireAsync(); await this.validatePost(post); this.postBuffer.push(post); this.postBufferLock.release(); } /** * Manipulate the page to stimulate a request */ async jump() { await this.page.keyboard.press("PageUp"); const jumpSize = this.graft ? 1 : this.jumpSize; for (let i = 0; i < jumpSize; i++) { await this.page.keyboard.press("End"); } // Move mouse randomly const width = this.page.viewport()["width"]; const height = this.page.viewport()["height"]; await this.page.mouse.move(Math.round(width * Math.random()), Math.round(height * Math.random())); ++this.jumps; } /** * Clear request and response buffers */ async initiateGraft() { // Check if enabled if (!this.enableGrafting) { return; } await this.progress(Progress.GRAFTING); this.executePlugins("grafting"); // Enable grafting this.graft = true; } /** * Read the posts that are pre-loaded on the page */ async scrapeDefaultPosts() { // Get shortcodes from page /* istanbul ignore next */ const shortCodes = await this.page.evaluate((url) => { return Array.from(document.links) .filter((link) => { return (link.href.startsWith(url) && link.href.split("/").length >= 2); }) .map((link) => { const linkSplit = link.href.split("/"); return linkSplit[linkSplit.length - 2]; }); }, this.defaultPostURL); // Add postPage promises for (const shortCode of shortCodes) { if (this.index < this.total || this.total === 0) { this.index++; this.pagePromises.push(this.postPage(shortCode, this.postPageRetries)); } else { this.finish(FinishedReasons.TOTAL_REACHED_PAGE); break; } } } addPlugins(plugins) { if (!plugins) { return; } for (const plugin of plugins) { for (const event of Object.keys(this.pluginFunctions)) { const pluginEvent = plugin[event + "Event"]; if (pluginEvent) { const context = { plugin, state: this, }; this.pluginFunctions[event].push(pluginEvent.bind(context)); } } } } executePlugins(event, ...args) { if (event in plugins_1.SyncPluginEvents) { for (const pluginFunction of this.pluginFunctions["construction"]) { pluginFunction(); } return; } return Promise.all( // @ts-ignore this.pluginFunctions[event].map((cb) => cb(...args))); } } exports.Instagram = Instagram; /** * The states of progress that the API can be in. Used to output status. */ var Progress; (function (Progress) { Progress["LAUNCHING"] = "Launching"; Progress["OPENING"] = "Navigating"; Progress["SCRAPING"] = "Scraping"; Progress["BRANCHING"] = "Branching"; Progress["GRAFTING"] = "Grafting"; Progress["CLOSING"] = "Closing"; Progress["PAUSED"] = "Paused"; Progress["ABORTED"] = "Request aborted"; })(Progress || (Progress = {})); /** * Reasons why the collection finished */ var FinishedReasons; (function (FinishedReasons) { // forceStop used FinishedReasons[FinishedReasons["FORCED_STOP"] = 0] = "FORCED_STOP"; // API response doesn't contain next page FinishedReasons[FinishedReasons["API_FINISHED"] = 1] = "API_FINISHED"; // Total posts required have been collected from the API FinishedReasons[FinishedReasons["TOTAL_REACHED_API"] = 2] = "TOTAL_REACHED_API"; // Total posts required have been collected from the default posts FinishedReasons[FinishedReasons["TOTAL_REACHED_PAGE"] = 3] = "TOTAL_REACHED_PAGE"; // No API response intercepted after interacting with page FinishedReasons[FinishedReasons["NO_RESPONSE"] = 4] = "NO_RESPONSE"; // Index hasn't increased after interacting with page FinishedReasons[FinishedReasons["NO_INCREMENT"] = 5] = "NO_INCREMENT"; })(FinishedReasons || (FinishedReasons = {})); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdGFncmFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaW5zdGFncmFtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSw0REFBbUM7QUFDbkMsa0RBQTBCO0FBQzFCLDZDQUF3QztBQUV4Qyx5REFBb0Q7QUFDcEQsMkRBQXNEO0FBQ3RELGlEQUFtQztBQUNuQyx5Q0FRbUI7QUFDbkIsaURBQW1DO0FBQ25DLDJDQU91QjtBQUV2QiwyQ0FBc0M7QUFVdEM7O0dBRUc7QUFDSCxNQUFhLFNBQVM7SUFzSmxCOzs7Ozs7OztPQVFHO0lBQ0gsWUFDSSxRQUFnQixFQUNoQixFQUFVLEVBQ1YsU0FBaUIsRUFDakIsU0FBaUIsRUFDakIsVUFBb0IsRUFBRSxFQUN0QixTQUF3QjtRQTVINUIsa0JBQWtCO1FBQ1gsWUFBTyxHQUFZLEtBQUssQ0FBQztRQUN6QixXQUFNLEdBQVksS0FBSyxDQUFDO1FBQ3hCLGFBQVEsR0FBWSxLQUFLLENBQUM7UUFHakMsaUJBQWlCO1FBQ1YsYUFBUSxHQUFXLHlDQUF5QyxDQUFDO1FBQzdELFlBQU8sR0FBVyw4QkFBOEIsQ0FBQztRQUNqRCxtQkFBYyxHQUFXLDhCQUE4QixDQUFDO1FBRS9ELGtDQUFrQztRQUMzQixZQUFPLEdBQVcsR0FBRyxDQUFDO1FBRTdCLGlCQUFpQjtRQUNWLGFBQVEsR0FBVyxDQUFDLENBQUM7UUFRNUIseUNBQXlDO1FBQ2xDLHlCQUFvQixHQUFtQixFQUFFLENBQUM7UUFFakQsY0FBYztRQUNHLFdBQU0sR0FBWSxLQUFLLENBQUM7UUFLakMsd0JBQW1CLEdBQVksSUFBSSxDQUFDO1FBSTVDLGtDQUFrQztRQUMxQixlQUFVLEdBQWUsRUFBRSxDQUFDO1FBQzVCLG1CQUFjLEdBQWMsSUFBSSxvQkFBUyxFQUFFLENBQUM7UUFFcEQseUNBQXlDO1FBQ2pDLGtCQUFhLEdBQWMsRUFBRSxDQUFDO1FBQzlCLHNCQUFpQixHQUFjLElBQUksb0JBQVMsRUFB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