Object.entries(require('./common.js')).forEach(([name, exported]) => global[name] = exported);
const url = "https://albertacannabis.org/";
const range = /(\d*\.?\d+)(?:[-−](\d*\.?\d+))?\s*(?:[A-Za-z%]+)/;
const quantityregex = /(?:(\d+)\s*(?:[Xx])\s*)?(\d*\.?\d+)\s*(?:[A-WYZa-wyz]+)/;
let anticaptcha = require('./anticaptcha.js')(process.env.ANTICAPTCHA_TOKEN);
const writeFile = util.promisify(require('fs').writeFile);
function captcha() {
return new Promise((resolve, reject) => {
anticaptcha.getBalance((err, balance) => {
if(err) {
reject(err);
} else {
if(balance > 0) {
console.log(balance + ' USD');
anticaptcha.createTaskProxyless((err, taskId) => {
if(err) {
reject(err);
} else {
console.log(taskId);
anticaptcha.getTaskSolution(taskId, (err, taskSolution) => {
if(err) {
reject(err);
} else {
resolve(taskSolution);
}
});
}
});
} else {
reject('insufficient funds to beat captcha');
}
}
});
});
}
async function cookieString(page) {
let cookieString = `"Cookie: `;
let cookies = await page.cookies();
for(let i = 0; i < cookies.length; ++i) {
if(cookies[i].name === 'ASP.NET_SessionId') {
cookieString += `ASP.NET_SessionId=${cookies[i].value}; `;
} else if(cookies[i].name === '__RequestVerificationToken') {
cookieString += `__RequestVerificationToken=${cookies[i].value}; `;
} else if(cookies[i].name === '.ASPXAUTH') {
cookieString += `.ASPXAUTH=${cookies[i].value}; `;
}
}
return cookieString.slice(0, -2) + `"`;
}
async function login(page, c, attempt) {
await page.goto(url + 'login');
await Promise.all([
page.waitForNavigation().then(async response => {
response = await response.json();
if(!response.Success) {
if(attempt > 4) {
console.error(response);
process.exit(1);
}
if(++attempt % 2) {
await new Promise(resolve => setTimeout(resolve, 1000));
await login(page, c, attempt);
} else {
await login(page, await captcha(), attempt);
}
}
}),
page.evaluate((solution, usr, pass) => {
let f = document.forms['LoginForm'];
let e = f.elements;
e['UserName'].value = usr;
e['UserName'].removeAttribute('disabled');
e['Password'].value = pass;
e['Password'].removeAttribute('disabled');
let re = document.createElement('input');
re.setAttribute('type', 'text');
re.setAttribute('name', 'g-recaptcha-response');
re.setAttribute('value', solution);
f.append(re);
f.submit();
}, c, process.env.AB_USER, process.env.AB_PASS)
]);
}
async function saveCookie(page) {
let cookies = await page.cookies();
for(let i = 0; i < cookies.length; ++i) {
cookies[i].expires = 2147483647;
}
await writeFile('ab.json', JSON.stringify(cookies, null, '\t'));
}
(async function() {
let start = new Date();
const browser = await puppeteer.launch({
args: ["--no-sandbox"],
headless: false
});
await postgres.connect();
await postgres.query('begin');
let storeID = (await postgres.query(`select ios_store_id('ab', 'alberta', 'canada', _url => $1, _delivery => true, _prepayment => true)`, [url])).rows[0].ios_store_id;
const page = await mnmalism((await browser.pages())[0]);
let ua = await page.evaluate('navigator.userAgent');
try {
await page.setCookie(...require('./ab.json'));
} catch(e) {}
await page.goto(url);
let csrf = await page.$eval('#_CRSFform > input[type=hidden]', el => el.value);
let curl;
curl = exec(`curl "https://albertacannabis.org/api/cxa/CatalogExtended/GetProductList" -H ${await cookieString(page)} -H "User-Agent: ${ua}" -H "__RequestVerificationToken: ${csrf}" --data "ps=1000&f=in_stock=true|false&sd=Asc&cci={DAAC337F-EC42-9DD5-33BC-851875C99BEB}&ci={C8173B3F-26C8-41AB-B042-9CAAD37B543B}&__RequestVerificationToken=${csrf}"`) .then(raw => [Object.values(groupBy(JSON.parse(raw.stdout).Variants, 'ProductId')), new Date()]);
if(await page.$eval('#header', el => /login/i.test(el.textContent))) {
anticaptcha.setWebsiteURL(url + 'login');
anticaptcha.setWebsiteKey('6LdU65cUAAAAAKzOnF9G9ken97cmhNWGl66lrGJE');
anticaptcha.setUserAgent(ua);
await login(page, await captcha(), 0);
await saveCookie(page);
}
console.log('logged in at: ', new Date());
let [products, APItouch] = await curl;
console.log('retrieved products from api at: ', APItouch);
let i = 0;
let progress = 0;
let gas = [];
require('fs').writeFileSync('wow', JSON.stringify(products));
async function deal(p) {
while(i < products.length) {
let idx = i++;
let type;
switch(products[idx][0].Format) {
case 'Dried Flower':
case 'Milled Flower':
type = 'flower';
break;
case 'Pre-Rolls':
type = 'preroll';
break;
case 'Prefilled Vape Cartridge':
case 'Vape Kit':
type = 'cartridge';
break;
case 'Disposable Vape Pen':
type = 'disposable';
break;
case 'Oil':
type = 'oil';
break;
case 'Oral Spray':
type = 'spray';
break;
case 'Capsules':
case 'Soft Gel':
type = 'capsule';
break;
case 'Tea Bags':
type = 'tea';
break;
case 'Iced Tea':
case 'Sodas':
case 'Sparkling Beverages':
type = 'rtd';
break;
case 'Distillate Powder':
type = 'powder';
break;
case 'Distillate Liquid':
type = 'liquid';
break;
case 'Soft Chews':
type = 'gummy';
break;
case 'Hard Candy':
type = 'mint';
break;
case 'Chocolate':
type = 'chocolate';
break;
case 'Cookies':
type = 'baked';
break;
default:
++progress;
continue;
}
await goto(p, url, products[idx][0].Link.substring(1), 0);
let [producer, brand, name, join_name, hack] = normalize(
products[idx][0].Brand,
products[idx][0].DisplayName,
type,
await p.$eval('.sku-lp', e => e.textContent.trim())
);
let blend = false;
if(products[idx][0].Type === 'Blend') {
blend = true;
let fuck = products[idx][0].Strain.match(/hybrid|indica|sativa/i)?.[0]?.toLowerCase();
if(fuck) {
products[idx][0].Type = fuck;
} else {
products[idx][0].Type = 'hybrid';
}
}
if(/blend/i.test(products[idx][0].Strain)) {
blend = true;
}
let terpenes = await p.$$eval('.item-terpene > h3', els => {
for(let j = els.length - 1; j >= 0; --j) {
els[j] = els[j].textContent;
if(els[j] === 'Other Terpenes') {
els.splice(j, 1);
} else {
els[j] = els[j].replace(/(\d*\.?\d+)([-−](\d*\.?\d+))?\s*([A-Za-z%]+)/, '').replace(/\s*\(.*\)\s*/, '').trim();
}
}
return els;
});
try {
terpenes.append((await p.$eval('#otherTerpenesInfoTip', el => el.textContent)).split(';'));
} catch(e) {}
let productID = (await postgres.query(`select ios_product_id($1, $2, $3, $4, $5, $6, $7, $8, $9, true, $10, $11)`, [
producer,
brand,
name,
join_name,
type,
products[idx][0].Type.toLowerCase(),
blend,
blend ? null : strain_clean(products[idx][0].Strain),
await p.$eval('.description', e => e.textContent.trim()),
terpenes,
brand === 'dosist' ? '.00225' : null
])).rows[0].ios_product_id;
for(let j = 0; j < products[idx].length; ++j) {
let portions = 1;
let quantity = products[idx][j].Quantity;
if(type === 'preroll' || type === 'capsule') {
gas.push(await p.$eval('#' + products[idx][j].VariantId, e => JSON.parse(e.dataset.variantData).disambiguatingdescription));
[,portions, quantity] = (await p.$eval('#' + products[idx][j].VariantId, e => JSON.parse(e.dataset.variantData).disambiguatingdescription)).match(quantityregex) ?? [];
portions = portions ?? 1;
if(!quantity) {
quantity = products[idx][j].Quantity
}
}
let params = [productID, portions, quantity];
let [variantID, g] = (await postgres.query(
`select ios_variant_id($1, $2, $3${type === 'oil' || type === 'spray' || type === 'capsule' ? `, _gram_equivalency => $${params.push(await p.$eval('#' + products[idx][j].VariantId, e => JSON.parse(e.dataset.variantData).equivalency))}` : ''})`,
params
)).rows[0].ios_variant_id.slice(1, -1).split(',');
try {
let [,minTHC, maxTHC] = products[idx][j].Thc.match(range);
let [,minCBD, maxCBD] = products[idx][j].Cbd.match(range);
await postgres.query(
`insert into menu_item (
variant_id,
store_id,
cbd,
thc,
price,
stock,
path
) values (
$1,
$2,
$3,
$4,
$5,
$6,
$7
) on conflict (variant_id, store_id) do update set cbd = excluded.cbd, thc = excluded.thc, price = excluded.price, stock = excluded.stock`,
[
variantID,
storeID,
`[${minCBD ?? maxCBD ?? 0},${maxCBD ?? minCBD ?? 0}]`,
`[${minTHC ?? maxTHC ?? 0},${maxTHC ?? minTHC ?? 0}]`,
products[idx][j].AdjustedPrice.replace('$', '').split(/\s+/)[0],
await p.$eval('[data-variantId="' + products[idx][j].VariantId + '"]', el => el.getAttribute('data-count')),
products[idx][j].Link
]
);
} catch(e) {
console.log(products[idx][j]);
console.log( `[${minCBD},${maxCBD}]`,
`[${minTHC},${maxTHC}]`,
products[idx][j].AdjustedPrice.replace('$', '').split(/\s+/)[0],
await p.$eval('[data-variantId="' + products[idx][j].VariantId + '"]', el => el.getAttribute('data-count')),
products[idx][j].Link);
}
}
++progress;
}
}
await Promise.all([
deal(page),
deal(await mnmalism(await browser.newPage())),
deal(await mnmalism(await browser.newPage()))
]);
console.log(products.length, progress)
require('fs').writeFileSync('gas', JSON.stringify(gas));
await saveCookie(page);
await browser.close();
await postgres.query('commit');
await postgres.end();
})();